fix: recovery from crash during optimization (#246)

This commit is contained in:
Qinren Zhou 2026-03-24 10:53:16 +08:00 committed by GitHub
parent 209ef1fba8
commit ae345ad070
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 420 additions and 53 deletions

View File

@ -187,7 +187,7 @@ class BufferStorage : public IndexStorage {
}
//! Open storage
int open(const std::string &path, bool /*create*/) override {
int open(const std::string &path, bool /*create_if_missing*/) override {
file_name_ = path;
buffer_pool_ = std::make_shared<ailego::VecBufferPool>(path);
buffer_pool_handle_ = std::make_shared<ailego::VecBufferPoolHandle>(

View File

@ -171,8 +171,8 @@ class MMapFileStorage : public IndexStorage {
}
//! Open storage
int open(const std::string &path, bool create) override {
if (!ailego::File::IsExist(path) && create) {
int open(const std::string &path, bool create_if_missing) override {
if (!ailego::File::IsExist(path) && create_if_missing) {
size_t last_slash = path.rfind('/');
if (last_slash != std::string::npos) {
ailego::File::MakePath(path.substr(0, last_slash));

View File

@ -958,7 +958,7 @@ std::vector<SegmentTask::Ptr> CollectionImpl::build_compact_task(
if (current_actual_doc_count + actual_doc_count >
max_doc_count_per_segment) {
// only create SegmentCompactTask when rebuild=true
task = SegmentTask::CreateComapctTask(
task = SegmentTask::CreateCompactTask(
CompactTask{path_, schema, current_group,
allocate_segment_id_for_tmp_segment(), filter,
!options_.enable_mmap_, concurrency});
@ -972,7 +972,7 @@ std::vector<SegmentTask::Ptr> CollectionImpl::build_compact_task(
current_group[0], "", nullptr, concurrency});
skip_task = current_group[0]->all_vector_index_ready();
} else {
task = SegmentTask::CreateComapctTask(
task = SegmentTask::CreateCompactTask(
CompactTask{path_, schema, current_group,
allocate_segment_id_for_tmp_segment(), nullptr,
!options_.enable_mmap_, concurrency});
@ -1001,7 +1001,7 @@ std::vector<SegmentTask::Ptr> CollectionImpl::build_compact_task(
task = SegmentTask::CreateCreateVectorIndexTask(
CreateVectorIndexTask{current_group[0], "", nullptr, concurrency});
} else {
task = SegmentTask::CreateComapctTask(CompactTask{
task = SegmentTask::CreateCompactTask(CompactTask{
path_, schema, current_group, allocate_segment_id_for_tmp_segment(),
rebuild ? filter : nullptr, !options_.enable_mmap_, concurrency});
}

View File

@ -1154,7 +1154,7 @@ struct Doc::ValueEqual {
const std::vector<float> &b) const {
if (a.size() != b.size()) return false;
for (size_t i = 0; i < a.size(); ++i)
if (std::fabs(a[i] - b[i]) >= 1e-6f) return false;
if (std::fabs(a[i] - b[i]) >= 1e-4f) return false;
return true;
}
@ -1162,7 +1162,7 @@ struct Doc::ValueEqual {
const std::vector<double> &b) const {
if (a.size() != b.size()) return false;
for (size_t i = 0; i < a.size(); ++i)
if (std::fabs(a[i] - b[i]) >= 1e-9) return false;
if (std::fabs(a[i] - b[i]) >= 1e-6) return false;
return true;
}
};

View File

@ -213,7 +213,7 @@ class SegmentImpl : public Segment,
const std::vector<int> &indices) const override;
ExecBatchPtr fetch(const std::vector<std::string> &columns,
int indice) const override;
int index) const override;
RecordBatchReaderPtr scan(
const std::vector<std::string> &columns) const override;
@ -1631,6 +1631,13 @@ Status SegmentImpl::create_vector_index(
std::string index_file_path = FileHelper::MakeVectorIndexPath(
path_, column, segment_meta_->id(), block_id);
if (FileHelper::FileExists(index_file_path)) {
LOG_WARN(
"Index file[%s] already exists (possible crash residue); cleaning "
"and overwriting.",
index_file_path.c_str());
FileHelper::RemoveFile(index_file_path);
}
auto vector_indexer = merge_vector_indexer(
index_file_path, column, *field_with_new_index_params, concurrency);
if (!vector_indexer.has_value()) {
@ -1668,6 +1675,13 @@ Status SegmentImpl::create_vector_index(
std::string index_file_path = FileHelper::MakeVectorIndexPath(
path_, column, segment_meta_->id(), block_id);
if (FileHelper::FileExists(index_file_path)) {
LOG_WARN(
"Index file[%s] already exists (possible crash residue); cleaning "
"and overwriting.",
index_file_path.c_str());
FileHelper::RemoveFile(index_file_path);
}
auto vector_indexer = merge_vector_indexer(index_file_path, column,
*field_with_flat, concurrency);
if (!vector_indexer.has_value()) {
@ -1700,6 +1714,13 @@ Status SegmentImpl::create_vector_index(
std::string index_file_path = FileHelper::MakeQuantizeVectorIndexPath(
path_, column, segment_meta_->id(), quant_block_id);
if (FileHelper::FileExists(index_file_path)) {
LOG_WARN(
"Index file[%s] already exists (possible crash residue); cleaning "
"and overwriting.",
index_file_path.c_str());
FileHelper::RemoveFile(index_file_path);
}
auto vector_indexer = merge_vector_indexer(
index_file_path, column, *field_with_new_index_params, concurrency);
if (!vector_indexer.has_value()) {
@ -1772,6 +1793,13 @@ Status SegmentImpl::create_vector_index(
std::string index_file_path = FileHelper::MakeQuantizeVectorIndexPath(
path_, column, segment_meta_->id(), quant_block_id);
if (FileHelper::FileExists(index_file_path)) {
LOG_WARN(
"Index file[%s] already exists (possible crash residue); cleaning "
"and overwriting.",
index_file_path.c_str());
FileHelper::RemoveFile(index_file_path);
}
auto vector_indexer = merge_vector_indexer(
index_file_path, column, *field_with_new_index_params, concurrency);
if (!vector_indexer.has_value()) {
@ -2031,7 +2059,7 @@ Status SegmentImpl::create_scalar_index(const std::vector<std::string> &columns,
s = SegmentHelper::ReduceScalarIndex(new_scalar_indexer, batch_value,
accu_doc_count);
if (!s.ok()) {
LOG_ERROR("Reduce Scalar Index faield, err: %s", s.message().c_str());
LOG_ERROR("Reduce Scalar Index failed, err: %s", s.message().c_str());
}
CHECK_RETURN_STATUS(s);

View File

@ -77,7 +77,7 @@ class Segment {
virtual Status drop_column(const std::string &column_name) = 0;
virtual Status create_all_vector_index(
int concurrency, SegmentMeta::Ptr *new_segmnet_meta,
int concurrency, SegmentMeta::Ptr *new_segment_meta,
std::unordered_map<std::string, VectorColumnIndexer::Ptr>
*vector_indexers,
std::unordered_map<std::string, VectorColumnIndexer::Ptr>
@ -86,14 +86,14 @@ class Segment {
// defined in segment.h cause it needs to access block_id generator
virtual Status create_vector_index(
const std::string &column, const IndexParams::Ptr &index_params,
int concurrency, SegmentMeta::Ptr *new_segmnet_meta,
int concurrency, SegmentMeta::Ptr *new_segment_meta,
std::unordered_map<std::string, VectorColumnIndexer::Ptr>
*vector_indexers,
std::unordered_map<std::string, VectorColumnIndexer::Ptr>
*quant_vector_indexers) = 0;
virtual Status drop_vector_index(
const std::string &column, SegmentMeta::Ptr *new_segmnet_meta,
const std::string &column, SegmentMeta::Ptr *new_segment_meta,
std::unordered_map<std::string, VectorColumnIndexer::Ptr>
*vector_indexers) = 0;

View File

@ -136,7 +136,7 @@ class SegmentTask {
std::variant<CompactTask, CreateVectorIndexTask, DropVectorIndexTask,
CreateScalarIndexTask, DropScalarIndexTask>;
static Ptr CreateComapctTask(const CompactTask &task) {
static Ptr CreateCompactTask(const CompactTask &task) {
return std::make_shared<SegmentTask>(task);
}

View File

@ -228,7 +228,7 @@ class IndexStorage : public IndexModule {
virtual int cleanup(void) = 0;
//! Open storage
virtual int open(const std::string &path, bool create) = 0;
virtual int open(const std::string &path, bool create_if_missing) = 0;
//! Flush storage
virtual int flush(void) = 0;

View File

@ -17,10 +17,9 @@ if(APPLE)
endif()
# Build data_generator executable
cc_binary(
NAME data_generator
LIBS zvec_db
# Common libraries
set(CRASH_RECOVERY_COMMON_LIBS
zvec_db
zvec_proto
core_knn_flat
core_knn_flat_sparse
@ -34,36 +33,41 @@ cc_binary(
core_quantizer
${CMAKE_THREAD_LIBS_INIT}
${CMAKE_DL_LIBS}
)
# Build data_generator executable
cc_binary(
NAME data_generator
LIBS ${CRASH_RECOVERY_COMMON_LIBS}
SRCS data_generator.cc
INCS .. ../../src
LDFLAGS ${APPLE_FRAMEWORK_LIBS}
)
# Build collection_optimizer executable
cc_binary(
NAME collection_optimizer
LIBS ${CRASH_RECOVERY_COMMON_LIBS}
SRCS collection_optimizer.cc
INCS .. ../../src
LDFLAGS ${APPLE_FRAMEWORK_LIBS}
)
# Build test executables
file(GLOB ALL_TEST_SRCS *_test.cc)
foreach(CC_SRCS ${ALL_TEST_SRCS})
get_filename_component(CC_TARGET ${CC_SRCS} NAME_WE)
cc_gmock(
NAME ${CC_TARGET} STRICT
LIBS zvec_db
zvec_proto
core_knn_flat
core_knn_flat_sparse
core_knn_hnsw
core_knn_hnsw_sparse
core_knn_ivf
core_knn_hnsw_rabitq
core_mix_reducer
core_metric
core_utility
core_quantizer
${CMAKE_THREAD_LIBS_INIT}
${CMAKE_DL_LIBS}
LIBS ${CRASH_RECOVERY_COMMON_LIBS}
SRCS ${CC_SRCS}
INCS .. ../../src
LDFLAGS ${APPLE_FRAMEWORK_LIBS}
)
add_dependencies(${CC_TARGET} data_generator)
add_dependencies(${CC_TARGET} collection_optimizer)
cc_test_suite(zvec_crash_recovery ${CC_TARGET})
endforeach()

View File

@ -0,0 +1,106 @@
// Copyright 2025-present the zvec project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <unistd.h>
#include <filesystem>
#include <zvec/db/collection.h>
#include <zvec/db/options.h>
#include "zvec/ailego/logger/logger.h"
struct Config {
std::string path;
};
bool ParseArgs(int argc, char **argv, Config &config) {
for (int i = 1; i < argc; i++) {
std::string arg = argv[i];
if (arg == "--path" && i + 1 < argc) {
config.path = argv[++i];
} else if (arg == "--help" || arg == "-h") {
return false;
}
}
// Validate required arguments
if (config.path.empty()) {
return false;
}
return true;
}
void PrintUsage(const char *program) {
std::cout << "Usage: " << program << " --path <collection_path>" << std::endl;
std::cout << std::endl;
std::cout << "Arguments:" << std::endl;
std::cout << " --path Path to the collection (required)"
<< std::endl;
}
int main(int argc, char **argv) {
Config config;
// Parse arguments
if (!ParseArgs(argc, argv, config)) {
PrintUsage(argv[0]);
return 1;
}
try {
std::filesystem::path cwd = std::filesystem::current_path();
std::cout << "[collection_optimizer] Current Working Directory: "
<< cwd.string() << std::endl;
} catch (const std::filesystem::filesystem_error &e) {
std::cout << "[collection_optimizer] Failed to get the current working "
"directory: "
<< e.what() << std::endl;
}
std::cout << "Configuration:" << std::endl;
std::cout << " Path: " << config.path << std::endl;
std::cout << std::endl;
auto result =
zvec::Collection::Open(config.path, zvec::CollectionOptions{false, true});
if (!result) {
LOG_ERROR("Failed to open collection[%s]: %s", config.path.c_str(),
result.error().c_str());
return -1;
}
auto collection = result.value();
std::cout << "Collection[" << config.path.c_str() << "] opened successfully"
<< std::endl;
// Print initial stats
std::cout << "Initial stats: " << collection->Stats()->to_string_formatted()
<< std::endl;
auto s = collection->Optimize();
if (s.ok()) {
std::cout << "Optimize completed successfully" << std::endl;
// Print final stats
std::cout << "Final stats: " << collection->Stats()->to_string_formatted() << std::endl;
return 0;
} else {
std::cout << "Optimize failed: " << s.message() << std::endl;
return 1;
}
}

View File

@ -0,0 +1,229 @@
// Copyright 2025-present the zvec project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <csignal>
#include <filesystem>
#include <thread>
#include <gtest/gtest.h>
#include <zvec/db/collection.h>
#include <zvec/db/doc.h>
#include <zvec/db/schema.h>
#include "utility.h"
namespace zvec {
static std::string optimizer_bin_;
const std::string collection_name_{"optimize_recovery_test"};
const std::string dir_path_{"optimize_recovery_test_db"};
const zvec::CollectionOptions options_{false, true, 256 * 1024};
const int batch_size{50};
const int num_batches{1000};
static std::string LocateOptimizeGenerator() {
namespace fs = std::filesystem;
const std::vector<std::string> candidates{"./collection_optimizer",
"./bin/collection_optimizer"};
for (const auto &p : candidates) {
if (fs::exists(p)) {
return fs::canonical(p).string();
}
}
throw std::runtime_error("collection_optimizer binary not found");
}
void RunOptimizer(const std::string &path) {
pid_t pid = fork();
ASSERT_GE(pid, 0);
if (pid == 0) { // Child process
char arg_path[] = "--path";
char *args[] = {const_cast<char *>(optimizer_bin_.c_str()), arg_path,
const_cast<char *>(path.c_str()), nullptr};
execvp(args[0], args);
perror("execvp failed");
_exit(1);
}
int status;
waitpid(pid, &status, 0);
ASSERT_TRUE(WIFEXITED(status))
<< "Child process did not exit normally. Terminated by signal?";
int exit_code = WEXITSTATUS(status);
ASSERT_EQ(exit_code, 0) << "optimizer failed with exit code: " << exit_code;
}
void RunOptimizerAndCrash(const std::string &path, int seconds) {
pid_t pid = fork();
ASSERT_GE(pid, 0);
if (pid == 0) { // Child process
char arg_path[] = "--path";
char *args[] = {const_cast<char *>(optimizer_bin_.c_str()), arg_path,
const_cast<char *>(path.c_str()), nullptr};
execvp(args[0], args);
perror("execvp failed");
_exit(1);
}
std::this_thread::sleep_for(std::chrono::seconds(seconds));
if (kill(pid, 0) == 0) {
kill(pid, SIGKILL);
}
int status;
waitpid(pid, &status, 0);
ASSERT_TRUE(WIFSIGNALED(status))
<< "Child process was not killed by a signal. It exited normally?";
}
class OptimizeRecoveryTest : public ::testing::Test {
protected:
void SetUp() override {
system("rm -rf ./optimize_recovery_test_db");
ASSERT_NO_THROW(optimizer_bin_ = LocateOptimizeGenerator());
}
void TearDown() override {
system("rm -rf ./optimize_recovery_test_db");
}
};
TEST_F(OptimizeRecoveryTest, CrashDuringOptimize) {
{ // Create a collection and insert some documents
auto schema = CreateTestSchema(collection_name_);
auto result = Collection::CreateAndOpen(dir_path_, *schema, options_);
ASSERT_TRUE(result.has_value());
auto collection = result.value();
for (int batch = 0; batch < num_batches; batch++) {
std::vector<Doc> docs;
for (int i = 0; i < batch_size; i++) {
docs.push_back(CreateTestDoc(batch * batch_size + i, 0));
}
auto write_result = collection->Insert(docs);
ASSERT_TRUE(write_result);
for (auto &s : write_result.value()) {
ASSERT_TRUE(s.ok());
}
}
ASSERT_EQ(collection->Stats()->doc_count, num_batches * batch_size);
collection.reset();
}
RunOptimizerAndCrash(dir_path_, 4);
{ // Open the collection and verify data integrity
auto result = Collection::Open(dir_path_, options_);
ASSERT_TRUE(result.has_value())
<< "Failed to reopen collection after crash. "
"Recovery mechanism may be broken.";
auto collection = result.value();
uint64_t doc_count{collection->Stats().value().doc_count};
ASSERT_EQ(doc_count, num_batches * batch_size);
for (uint64_t doc_id = 0; doc_id < doc_count; doc_id++) {
Doc expected_doc = CreateTestDoc(doc_id, 0);
std::vector<std::string> pks{};
pks.emplace_back(expected_doc.pk());
if (auto res = collection->Fetch(pks); res) {
auto map = res.value();
if (map.find(expected_doc.pk()) == map.end()) {
FAIL() << "Returned map does not contain doc[" << expected_doc.pk()
<< "]";
}
const auto actual_doc = map.at(expected_doc.pk());
ASSERT_EQ(*actual_doc, expected_doc)
<< "Data mismatch for doc[" << expected_doc.pk() << "]";
} else {
FAIL() << "Failed to fetch doc[" << expected_doc.pk() << "]";
}
}
VectorQuery query;
query.topk_ = 10;
std::vector<float> feature(128, 0.0);
query.query_vector_.assign((const char *)feature.data(),
feature.size() * sizeof(float));
query.field_name_ = "dense_fp32_field";
auto query_result = collection->Query(query);
ASSERT_TRUE(query_result);
auto doc_list = query_result.value();
ASSERT_EQ(doc_list.size(), 10);
ASSERT_EQ(doc_list[0]->pk(), "pk_0");
// Insert some more documents
for (int batch = num_batches; batch < num_batches + 500; batch++) {
std::vector<Doc> docs;
for (int i = 0; i < batch_size; i++) {
docs.push_back(CreateTestDoc(batch * batch_size + i, 0));
}
auto write_result = collection->Insert(docs);
ASSERT_TRUE(write_result);
for (auto &s : write_result.value()) {
ASSERT_TRUE(s.ok());
}
}
collection.reset();
}
RunOptimizer(dir_path_);
// Open the collection and verify data integrity
auto result = Collection::Open(dir_path_, options_);
ASSERT_TRUE(result.has_value()) << "Failed to reopen collection after crash. "
"Recovery mechanism may be broken.";
auto collection = result.value();
uint64_t doc_count{collection->Stats().value().doc_count};
ASSERT_EQ(doc_count, (num_batches + 500) * batch_size);
for (uint64_t doc_id = 0; doc_id < doc_count; doc_id++) {
Doc expected_doc = CreateTestDoc(doc_id, 0);
std::vector<std::string> pks{};
pks.emplace_back(expected_doc.pk());
if (auto res = collection->Fetch(pks); res) {
auto map = res.value();
if (map.find(expected_doc.pk()) == map.end()) {
FAIL() << "Returned map does not contain doc[" << expected_doc.pk()
<< "]";
}
const auto actual_doc = map.at(expected_doc.pk());
ASSERT_EQ(*actual_doc, expected_doc)
<< "Data mismatch for doc[" << expected_doc.pk() << "]";
} else {
FAIL() << "Failed to fetch doc[" << expected_doc.pk() << "]";
}
}
VectorQuery query;
query.topk_ = 10;
std::vector<float> feature(128, 0.0);
query.query_vector_.assign((const char *)feature.data(),
feature.size() * sizeof(float));
query.field_name_ = "dense_fp32_field";
auto query_result = collection->Query(query);
ASSERT_TRUE(query_result);
auto doc_list = query_result.value();
ASSERT_EQ(doc_list.size(), 10);
ASSERT_EQ(doc_list[0]->pk(), "pk_0");
}
} // namespace zvec

View File

@ -49,7 +49,7 @@ inline CollectionSchema::Ptr CreateTestSchema(
"array_string_field", DataType::ARRAY_STRING, false));
schema->add_field(std::make_shared<FieldSchema>(
"dense_fp32_field", DataType::VECTOR_FP32, 128, false,
std::make_shared<HnswIndexParams>(MetricType::COSINE)));
std::make_shared<HnswIndexParams>(MetricType::L2)));
schema->add_field(std::make_shared<FieldSchema>(
"sparse_fp32_field", DataType::SPARSE_VECTOR_FP32, 0, false,
std::make_shared<HnswIndexParams>(MetricType::IP)));

View File

@ -27,9 +27,9 @@ namespace zvec {
static std::string data_generator_bin_;
const std::string collection_name_{"crash_test"};
const std::string dir_path_{"crash_test_db"};
const zvec::CollectionOptions options_{false, true};
const std::string collection_name_{"write_recovery_test"};
const std::string dir_path_{"write_recovery_test_db"};
const zvec::CollectionOptions options_{false, true, 256 * 1024};
static std::string LocateDataGenerator() {
@ -126,12 +126,12 @@ void RunGeneratorAndCrash(const std::string &start, const std::string &end,
class CrashRecoveryTest : public ::testing::Test {
protected:
void SetUp() override {
system("rm -rf ./crash_test_db");
system("rm -rf ./write_recovery_test_db");
ASSERT_NO_THROW(data_generator_bin_ = LocateDataGenerator());
}
void TearDown() override {
system("rm -rf ./crash_test_db");
system("rm -rf ./write_recovery_test_db");
}
};
@ -140,7 +140,7 @@ TEST_F(CrashRecoveryTest, BasicInsertAndReopen) {
{
auto schema = CreateTestSchema(collection_name_);
auto result = Collection::CreateAndOpen(dir_path_, *schema, options_);
ASSERT_TRUE(result.has_value());
ASSERT_TRUE(result.has_value()) << result.error().message();
auto collection = result.value();
collection.reset();
}
@ -158,7 +158,7 @@ TEST_F(CrashRecoveryTest, CrashRecoveryDuringInsertion) {
{
auto schema = CreateTestSchema(collection_name_);
auto result = Collection::CreateAndOpen(dir_path_, *schema, options_);
ASSERT_TRUE(result.has_value());
ASSERT_TRUE(result.has_value()) << result.error().message();
auto collection = result.value();
collection.reset();
}
@ -197,7 +197,7 @@ TEST_F(CrashRecoveryTest, CrashRecoveryDuringUpsert) {
{
auto schema = CreateTestSchema(collection_name_);
auto result = Collection::CreateAndOpen(dir_path_, *schema, options_);
ASSERT_TRUE(result.has_value());
ASSERT_TRUE(result.has_value()) << result.error().message();
auto collection = result.value();
collection.reset();
}
@ -205,7 +205,7 @@ TEST_F(CrashRecoveryTest, CrashRecoveryDuringUpsert) {
RunGenerator("0", "5000", "insert", "0");
{
auto result = Collection::Open(dir_path_, options_);
ASSERT_TRUE(result.has_value());
ASSERT_TRUE(result.has_value()) << result.error().message();
auto collection = result.value();
ASSERT_EQ(collection->Stats().value().doc_count, 5000)
<< "Document count mismatch";
@ -250,7 +250,7 @@ TEST_F(CrashRecoveryTest, CrashRecoveryDuringUpdate) {
{
auto schema = CreateTestSchema(collection_name_);
auto result = Collection::CreateAndOpen(dir_path_, *schema, options_);
ASSERT_TRUE(result.has_value());
ASSERT_TRUE(result.has_value()) << result.error().message();
auto collection = result.value();
collection.reset();
}
@ -258,7 +258,7 @@ TEST_F(CrashRecoveryTest, CrashRecoveryDuringUpdate) {
RunGenerator("0", "18000", "upsert", "0");
{
auto result = Collection::Open(dir_path_, options_);
ASSERT_TRUE(result.has_value());
ASSERT_TRUE(result.has_value()) << result.error().message();
auto collection = result.value();
ASSERT_EQ(collection->Stats().value().doc_count, 18000)
<< "Document count mismatch";
@ -302,7 +302,7 @@ TEST_F(CrashRecoveryTest, CrashRecoveryDuringDelete) {
{
auto schema = CreateTestSchema(collection_name_);
auto result = Collection::CreateAndOpen(dir_path_, *schema, options_);
ASSERT_TRUE(result.has_value());
ASSERT_TRUE(result.has_value()) << result.error().message();
auto collection = result.value();
collection.reset();
}
@ -310,7 +310,7 @@ TEST_F(CrashRecoveryTest, CrashRecoveryDuringDelete) {
RunGenerator("0", "18000", "insert", "0");
{
auto result = Collection::Open(dir_path_, options_);
ASSERT_TRUE(result.has_value());
ASSERT_TRUE(result.has_value()) << result.error().message();
auto collection = result.value();
ASSERT_EQ(collection->Stats().value().doc_count, 18000)
<< "Document count mismatch";

View File

@ -121,7 +121,7 @@ TEST_F(SegmentHelperTest, CompactTask_General) {
);
// Create segment task
auto segment_task = SegmentTask::CreateComapctTask(task);
auto segment_task = SegmentTask::CreateCompactTask(task);
// Verify task creation
ASSERT_TRUE(segment_task != nullptr);
@ -214,7 +214,7 @@ TEST_F(SegmentHelperTest, CompactTask_ScalarIndex) {
);
// Create segment task
auto segment_task = SegmentTask::CreateComapctTask(task);
auto segment_task = SegmentTask::CreateCompactTask(task);
// Verify task creation
ASSERT_TRUE(segment_task != nullptr);
@ -307,7 +307,7 @@ TEST_F(SegmentHelperTest, CompactTask_VectorIndex) {
);
// Create segment task
auto segment_task = SegmentTask::CreateComapctTask(task);
auto segment_task = SegmentTask::CreateCompactTask(task);
// Verify task creation
ASSERT_TRUE(segment_task != nullptr);
@ -395,7 +395,7 @@ TEST_F(SegmentHelperTest, CompactTask_MultipleSegments) {
);
// Create segment task
auto segment_task = SegmentTask::CreateComapctTask(task);
auto segment_task = SegmentTask::CreateCompactTask(task);
// Verify task creation
ASSERT_TRUE(segment_task != nullptr);
@ -484,7 +484,7 @@ TEST_F(SegmentHelperTest, CompactTask_Filter) {
);
// Create and execute task
auto segment_task = SegmentTask::CreateComapctTask(task);
auto segment_task = SegmentTask::CreateCompactTask(task);
ASSERT_TRUE(segment_task != nullptr);
Status status = SegmentHelper::Execute(segment_task);
@ -563,7 +563,7 @@ TEST_F(SegmentHelperTest, CompactTask_FilterAll) {
);
// Create and execute task
auto segment_task = SegmentTask::CreateComapctTask(task);
auto segment_task = SegmentTask::CreateCompactTask(task);
ASSERT_TRUE(segment_task != nullptr);
Status status = SegmentHelper::Execute(segment_task);