mirror of
https://github.com/meilisearch/meilisearch.git
synced 2025-01-23 19:43:10 +08:00
396 lines
17 KiB
Rust
396 lines
17 KiB
Rust
use big_s::S;
|
||
use meili_snap::{json_string, snapshot};
|
||
use meilisearch_types::error::ErrorCode;
|
||
use meilisearch_types::tasks::{KindWithContent, Status};
|
||
use roaring::RoaringBitmap;
|
||
|
||
use crate::insta_snapshot::snapshot_index_scheduler;
|
||
use crate::test_utils::Breakpoint::*;
|
||
use crate::test_utils::{index_creation_task, replace_document_import_task};
|
||
use crate::{IndexScheduler, Query};
|
||
|
||
#[test]
|
||
fn register() {
|
||
// In this test, the handle doesn't make any progress, we only check that the tasks are registered
|
||
let (index_scheduler, mut _handle) = IndexScheduler::test(true, vec![]);
|
||
|
||
let kinds = [
|
||
index_creation_task("catto", "mouse"),
|
||
replace_document_import_task("catto", None, 0, 12),
|
||
replace_document_import_task("catto", None, 1, 50),
|
||
replace_document_import_task("doggo", Some("bone"), 2, 5000),
|
||
];
|
||
let (_, file) = index_scheduler.queue.create_update_file_with_uuid(0).unwrap();
|
||
file.persist().unwrap();
|
||
let (_, file) = index_scheduler.queue.create_update_file_with_uuid(1).unwrap();
|
||
file.persist().unwrap();
|
||
let (_, file) = index_scheduler.queue.create_update_file_with_uuid(2).unwrap();
|
||
file.persist().unwrap();
|
||
|
||
for (idx, kind) in kinds.into_iter().enumerate() {
|
||
let k = kind.as_kind();
|
||
let task = index_scheduler.register(kind, None, false).unwrap();
|
||
index_scheduler.assert_internally_consistent();
|
||
|
||
assert_eq!(task.uid, idx as u32);
|
||
assert_eq!(task.status, Status::Enqueued);
|
||
assert_eq!(task.kind.as_kind(), k);
|
||
}
|
||
|
||
snapshot!(snapshot_index_scheduler(&index_scheduler), name: "everything_is_successfully_registered");
|
||
}
|
||
|
||
#[test]
|
||
fn dry_run() {
|
||
let (index_scheduler, _handle) = IndexScheduler::test(true, vec![]);
|
||
|
||
let kind = KindWithContent::IndexCreation { index_uid: S("doggo"), primary_key: None };
|
||
let task = index_scheduler.register(kind, None, true).unwrap();
|
||
snapshot!(task.uid, @"0");
|
||
snapshot!(snapshot_index_scheduler(&index_scheduler), @r"
|
||
### Autobatching Enabled = true
|
||
### Processing batch None:
|
||
[]
|
||
----------------------------------------------------------------------
|
||
### All Tasks:
|
||
----------------------------------------------------------------------
|
||
### Status:
|
||
----------------------------------------------------------------------
|
||
### Kind:
|
||
----------------------------------------------------------------------
|
||
### Index Tasks:
|
||
----------------------------------------------------------------------
|
||
### Index Mapper:
|
||
|
||
----------------------------------------------------------------------
|
||
### Canceled By:
|
||
|
||
----------------------------------------------------------------------
|
||
### Enqueued At:
|
||
----------------------------------------------------------------------
|
||
### Started At:
|
||
----------------------------------------------------------------------
|
||
### Finished At:
|
||
----------------------------------------------------------------------
|
||
### All Batches:
|
||
----------------------------------------------------------------------
|
||
### Batch to tasks mapping:
|
||
----------------------------------------------------------------------
|
||
### Batches Status:
|
||
----------------------------------------------------------------------
|
||
### Batches Kind:
|
||
----------------------------------------------------------------------
|
||
### Batches Index Tasks:
|
||
----------------------------------------------------------------------
|
||
### Batches Enqueued At:
|
||
----------------------------------------------------------------------
|
||
### Batches Started At:
|
||
----------------------------------------------------------------------
|
||
### Batches Finished At:
|
||
----------------------------------------------------------------------
|
||
### File Store:
|
||
|
||
----------------------------------------------------------------------
|
||
");
|
||
|
||
let kind = KindWithContent::IndexCreation { index_uid: S("doggo"), primary_key: None };
|
||
let task = index_scheduler.register(kind, Some(12), true).unwrap();
|
||
snapshot!(task.uid, @"12");
|
||
snapshot!(snapshot_index_scheduler(&index_scheduler), @r"
|
||
### Autobatching Enabled = true
|
||
### Processing batch None:
|
||
[]
|
||
----------------------------------------------------------------------
|
||
### All Tasks:
|
||
----------------------------------------------------------------------
|
||
### Status:
|
||
----------------------------------------------------------------------
|
||
### Kind:
|
||
----------------------------------------------------------------------
|
||
### Index Tasks:
|
||
----------------------------------------------------------------------
|
||
### Index Mapper:
|
||
|
||
----------------------------------------------------------------------
|
||
### Canceled By:
|
||
|
||
----------------------------------------------------------------------
|
||
### Enqueued At:
|
||
----------------------------------------------------------------------
|
||
### Started At:
|
||
----------------------------------------------------------------------
|
||
### Finished At:
|
||
----------------------------------------------------------------------
|
||
### All Batches:
|
||
----------------------------------------------------------------------
|
||
### Batch to tasks mapping:
|
||
----------------------------------------------------------------------
|
||
### Batches Status:
|
||
----------------------------------------------------------------------
|
||
### Batches Kind:
|
||
----------------------------------------------------------------------
|
||
### Batches Index Tasks:
|
||
----------------------------------------------------------------------
|
||
### Batches Enqueued At:
|
||
----------------------------------------------------------------------
|
||
### Batches Started At:
|
||
----------------------------------------------------------------------
|
||
### Batches Finished At:
|
||
----------------------------------------------------------------------
|
||
### File Store:
|
||
|
||
----------------------------------------------------------------------
|
||
");
|
||
}
|
||
|
||
#[test]
|
||
fn basic_set_taskid() {
|
||
let (index_scheduler, _handle) = IndexScheduler::test(true, vec![]);
|
||
|
||
let kind = KindWithContent::IndexCreation { index_uid: S("doggo"), primary_key: None };
|
||
let task = index_scheduler.register(kind, None, false).unwrap();
|
||
snapshot!(task.uid, @"0");
|
||
|
||
let kind = KindWithContent::IndexCreation { index_uid: S("doggo"), primary_key: None };
|
||
let task = index_scheduler.register(kind, Some(12), false).unwrap();
|
||
snapshot!(task.uid, @"12");
|
||
|
||
let kind = KindWithContent::IndexCreation { index_uid: S("doggo"), primary_key: None };
|
||
let error = index_scheduler.register(kind, Some(5), false).unwrap_err();
|
||
snapshot!(error, @"Received bad task id: 5 should be >= to 13.");
|
||
}
|
||
|
||
#[test]
|
||
fn test_disable_auto_deletion_of_tasks() {
|
||
let (index_scheduler, mut handle) = IndexScheduler::test_with_custom_config(vec![], |config| {
|
||
config.cleanup_enabled = false;
|
||
config.max_number_of_tasks = 2;
|
||
});
|
||
|
||
index_scheduler
|
||
.register(
|
||
KindWithContent::IndexCreation { index_uid: S("doggo"), primary_key: None },
|
||
None,
|
||
false,
|
||
)
|
||
.unwrap();
|
||
handle.advance_one_successful_batch();
|
||
|
||
index_scheduler
|
||
.register(
|
||
KindWithContent::IndexCreation { index_uid: S("doggo"), primary_key: None },
|
||
None,
|
||
false,
|
||
)
|
||
.unwrap();
|
||
handle.advance_one_failed_batch();
|
||
|
||
// at this point the max number of tasks is reached
|
||
// we can still enqueue multiple tasks
|
||
index_scheduler
|
||
.register(
|
||
KindWithContent::IndexCreation { index_uid: S("doggo"), primary_key: None },
|
||
None,
|
||
false,
|
||
)
|
||
.unwrap();
|
||
index_scheduler
|
||
.register(
|
||
KindWithContent::IndexCreation { index_uid: S("doggo"), primary_key: None },
|
||
None,
|
||
false,
|
||
)
|
||
.unwrap();
|
||
|
||
let rtxn = index_scheduler.env.read_txn().unwrap();
|
||
let proc = index_scheduler.processing_tasks.read().unwrap();
|
||
let tasks =
|
||
index_scheduler.queue.get_task_ids(&rtxn, &Query { ..Default::default() }, &proc).unwrap();
|
||
let tasks = index_scheduler.queue.tasks.get_existing_tasks(&rtxn, tasks).unwrap();
|
||
snapshot!(json_string!(tasks, { "[].enqueuedAt" => "[date]", "[].startedAt" => "[date]", "[].finishedAt" => "[date]" }), name: "task_queue_is_full");
|
||
drop(rtxn);
|
||
drop(proc);
|
||
|
||
// now we're above the max number of tasks
|
||
// and if we try to advance in the tick function no new task deletion should be enqueued
|
||
handle.advance_till([Start, BatchCreated]);
|
||
let rtxn = index_scheduler.env.read_txn().unwrap();
|
||
let proc = index_scheduler.processing_tasks.read().unwrap();
|
||
let tasks =
|
||
index_scheduler.queue.get_task_ids(&rtxn, &Query { ..Default::default() }, &proc).unwrap();
|
||
let tasks = index_scheduler.queue.tasks.get_existing_tasks(&rtxn, tasks).unwrap();
|
||
snapshot!(json_string!(tasks, { "[].enqueuedAt" => "[date]", "[].startedAt" => "[date]", "[].finishedAt" => "[date]", ".**.original_filter" => "[filter]", ".**.query" => "[query]" }), name: "task_deletion_have_not_been_enqueued");
|
||
drop(rtxn);
|
||
drop(proc);
|
||
}
|
||
|
||
#[test]
|
||
fn test_auto_deletion_of_tasks() {
|
||
let (index_scheduler, mut handle) = IndexScheduler::test_with_custom_config(vec![], |config| {
|
||
config.max_number_of_tasks = 2;
|
||
});
|
||
|
||
index_scheduler
|
||
.register(
|
||
KindWithContent::IndexCreation { index_uid: S("doggo"), primary_key: None },
|
||
None,
|
||
false,
|
||
)
|
||
.unwrap();
|
||
handle.advance_one_successful_batch();
|
||
|
||
index_scheduler
|
||
.register(
|
||
KindWithContent::IndexCreation { index_uid: S("doggo"), primary_key: None },
|
||
None,
|
||
false,
|
||
)
|
||
.unwrap();
|
||
handle.advance_one_failed_batch();
|
||
|
||
// at this point the max number of tasks is reached
|
||
// we can still enqueue multiple tasks
|
||
index_scheduler
|
||
.register(
|
||
KindWithContent::IndexCreation { index_uid: S("doggo"), primary_key: None },
|
||
None,
|
||
false,
|
||
)
|
||
.unwrap();
|
||
index_scheduler
|
||
.register(
|
||
KindWithContent::IndexCreation { index_uid: S("doggo"), primary_key: None },
|
||
None,
|
||
false,
|
||
)
|
||
.unwrap();
|
||
|
||
let rtxn = index_scheduler.env.read_txn().unwrap();
|
||
let proc = index_scheduler.processing_tasks.read().unwrap();
|
||
let tasks =
|
||
index_scheduler.queue.get_task_ids(&rtxn, &Query { ..Default::default() }, &proc).unwrap();
|
||
let tasks = index_scheduler.queue.tasks.get_existing_tasks(&rtxn, tasks).unwrap();
|
||
snapshot!(json_string!(tasks, { "[].enqueuedAt" => "[date]", "[].startedAt" => "[date]", "[].finishedAt" => "[date]" }), name: "task_queue_is_full");
|
||
drop(rtxn);
|
||
drop(proc);
|
||
|
||
// now we're above the max number of tasks
|
||
// and if we try to advance in the tick function a new task deletion should be enqueued
|
||
handle.advance_till([Start, BatchCreated]);
|
||
let rtxn = index_scheduler.env.read_txn().unwrap();
|
||
let proc = index_scheduler.processing_tasks.read().unwrap();
|
||
let tasks =
|
||
index_scheduler.queue.get_task_ids(&rtxn, &Query { ..Default::default() }, &proc).unwrap();
|
||
let tasks = index_scheduler.queue.tasks.get_existing_tasks(&rtxn, tasks).unwrap();
|
||
snapshot!(json_string!(tasks, { "[].enqueuedAt" => "[date]", "[].startedAt" => "[date]", "[].finishedAt" => "[date]", ".**.original_filter" => "[filter]", ".**.query" => "[query]" }), name: "task_deletion_have_been_enqueued");
|
||
drop(rtxn);
|
||
drop(proc);
|
||
|
||
handle.advance_till([InsideProcessBatch, ProcessBatchSucceeded, AfterProcessing]);
|
||
let rtxn = index_scheduler.env.read_txn().unwrap();
|
||
let proc = index_scheduler.processing_tasks.read().unwrap();
|
||
let tasks =
|
||
index_scheduler.queue.get_task_ids(&rtxn, &Query { ..Default::default() }, &proc).unwrap();
|
||
let tasks = index_scheduler.queue.tasks.get_existing_tasks(&rtxn, tasks).unwrap();
|
||
snapshot!(json_string!(tasks, { "[].enqueuedAt" => "[date]", "[].startedAt" => "[date]", "[].finishedAt" => "[date]", ".**.original_filter" => "[filter]", ".**.query" => "[query]" }), name: "task_deletion_have_been_processed");
|
||
drop(rtxn);
|
||
drop(proc);
|
||
|
||
handle.advance_one_failed_batch();
|
||
// a new task deletion has been enqueued
|
||
handle.advance_one_successful_batch();
|
||
let rtxn = index_scheduler.env.read_txn().unwrap();
|
||
let proc = index_scheduler.processing_tasks.read().unwrap();
|
||
let tasks =
|
||
index_scheduler.queue.get_task_ids(&rtxn, &Query { ..Default::default() }, &proc).unwrap();
|
||
let tasks = index_scheduler.queue.tasks.get_existing_tasks(&rtxn, tasks).unwrap();
|
||
snapshot!(json_string!(tasks, { "[].enqueuedAt" => "[date]", "[].startedAt" => "[date]", "[].finishedAt" => "[date]", ".**.original_filter" => "[filter]", ".**.query" => "[query]" }), name: "after_the_second_task_deletion");
|
||
drop(rtxn);
|
||
drop(proc);
|
||
|
||
handle.advance_one_failed_batch();
|
||
handle.advance_one_successful_batch();
|
||
let rtxn = index_scheduler.env.read_txn().unwrap();
|
||
let proc = index_scheduler.processing_tasks.read().unwrap();
|
||
let tasks =
|
||
index_scheduler.queue.get_task_ids(&rtxn, &Query { ..Default::default() }, &proc).unwrap();
|
||
let tasks = index_scheduler.queue.tasks.get_existing_tasks(&rtxn, tasks).unwrap();
|
||
snapshot!(json_string!(tasks, { "[].enqueuedAt" => "[date]", "[].startedAt" => "[date]", "[].finishedAt" => "[date]", ".**.original_filter" => "[filter]", ".**.query" => "[query]" }), name: "everything_has_been_processed");
|
||
drop(rtxn);
|
||
drop(proc);
|
||
}
|
||
|
||
#[test]
|
||
fn test_task_queue_is_full() {
|
||
let (index_scheduler, mut handle) = IndexScheduler::test_with_custom_config(vec![], |config| {
|
||
// that's the minimum map size possible
|
||
config.task_db_size = 1048576;
|
||
});
|
||
|
||
index_scheduler
|
||
.register(
|
||
KindWithContent::IndexCreation { index_uid: S("doggo"), primary_key: None },
|
||
None,
|
||
false,
|
||
)
|
||
.unwrap();
|
||
handle.advance_one_successful_batch();
|
||
// on average this task takes ~600 bytes
|
||
loop {
|
||
let result = index_scheduler.register(
|
||
KindWithContent::IndexCreation { index_uid: S("doggo"), primary_key: None },
|
||
None,
|
||
false,
|
||
);
|
||
if result.is_err() {
|
||
break;
|
||
}
|
||
handle.advance_one_failed_batch();
|
||
}
|
||
index_scheduler.assert_internally_consistent();
|
||
|
||
// at this point the task DB shoud have reached its limit and we should not be able to register new tasks
|
||
let result = index_scheduler
|
||
.register(
|
||
KindWithContent::IndexCreation { index_uid: S("doggo"), primary_key: None },
|
||
None,
|
||
false,
|
||
)
|
||
.unwrap_err();
|
||
snapshot!(result, @"Meilisearch cannot receive write operations because the limit of the task database has been reached. Please delete tasks to continue performing write operations.");
|
||
// we won't be able to test this error in an integration test thus as a best effort test I still ensure the error return the expected error code
|
||
snapshot!(format!("{:?}", result.error_code()), @"NoSpaceLeftOnDevice");
|
||
|
||
// Even the task deletion that doesn't delete anything shouldn't be accepted
|
||
let result = index_scheduler
|
||
.register(
|
||
KindWithContent::TaskDeletion { query: S("test"), tasks: RoaringBitmap::new() },
|
||
None,
|
||
false,
|
||
)
|
||
.unwrap_err();
|
||
snapshot!(result, @"Meilisearch cannot receive write operations because the limit of the task database has been reached. Please delete tasks to continue performing write operations.");
|
||
// we won't be able to test this error in an integration test thus as a best effort test I still ensure the error return the expected error code
|
||
snapshot!(format!("{:?}", result.error_code()), @"NoSpaceLeftOnDevice");
|
||
|
||
// But a task deletion that delete something should works
|
||
index_scheduler
|
||
.register(
|
||
KindWithContent::TaskDeletion { query: S("test"), tasks: (0..100).collect() },
|
||
None,
|
||
false,
|
||
)
|
||
.unwrap();
|
||
handle.advance_one_successful_batch();
|
||
|
||
// Now we should be able to enqueue a few tasks again
|
||
index_scheduler
|
||
.register(
|
||
KindWithContent::IndexCreation { index_uid: S("doggo"), primary_key: None },
|
||
None,
|
||
false,
|
||
)
|
||
.unwrap();
|
||
handle.advance_one_failed_batch();
|
||
}
|