You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

RocksDB单列族配置固定前缀提取器启动失败求助

RocksDB列族级前缀提取器配置问题

我需要在RocksDB的特定列族中设置固定长度前缀,该列族用于实现两个现有「全键索引」列族之间的连接索引。虽然ColumnFamilyOptions中明确定义了prefix_extractor,但列族文档未提及该提取器的列族级定制说明,不过理论上应该支持。

我编写的测试代码首次运行时可初始化数据库并添加指定描述符的列族,但第二次运行测试可执行文件时,无法通过列族描述符加载数据库,相关代码如下:

#include <cassert>
#include <cstdint>
#include <iostream>
#include <memory>
#include "rocksdb/db.h"
#include "rocksdb/options.h"
#include "rocksdb/slice_transform.h"
#include "rocksdb/table.h"

namespace rdb = rocksdb;

int main()  {

rdb::Options options;

options.write_buffer_size = (1U << 28);
options.max_open_files = (1U << 10);
options.create_if_missing = true;
options.wal_bytes_per_sync = 32768;
options.bytes_per_sync = 32768;
options.recycle_log_file_num = 8;

rdb::BlockBasedTableOptions blockbased_options;
blockbased_options.block_size = 1ULL << 17;
blockbased_options.enable_index_compression = false;
options.table_factory.reset(rdb::NewBlockBasedTableFactory(blockbased_options));
options.whole_key_filtering = false;
std::vector<rdb::ColumnFamilyDescriptor> cf_descriptors;

{
    rdb::ColumnFamilyOptions cf_normal_options;
    cf_descriptors.emplace_back("normal_cf", cf_normal_options);

    rdb::ColumnFamilyOptions cf_key_prefix_options;
    blockbased_options.whole_key_filtering = false;
    cf_pwo_edge_options.table_factory.reset(rdb::NewBlockBasedTableFactory(blockbased_options));
    cf_pwo_edge_options.prefix_extractor.reset( rdb::NewFixedPrefixTransform( sizeof(std::uint64_t) ) );
    cf_descriptors.emplace_back("key_prefix_cf", cf_key_prefix_options);
}

std::unique_ptr<rdb::DB> db;
std::vector< std::unique_ptr< rdb::ColumnFamilyHandle > > cf_safe_handles;
{
    rdb::DB* db_temp;
    std::vector<rdb::ColumnFamilyHandle*> cf_handles;
    auto s = rdb::DB::Open(options, "/tmp/test_cf", cf_descriptors, &cf_handles, &db_temp);

    if (!s.ok())
    {
        std::cout << "unable to open db with column family descriptors " << std::endl;
        s = rdb::DB::Open(options, "/tmp/test_cf", &db_temp);

        if (!s.ok())
        {
            std::cout << "unable to open uninitialized db " << std::endl;
            std::exit(1);
        }

        if (nullptr == db_temp)
        {
            std::cout << "db_temp pointer uninitialized " << std::endl;
            std::exit(1);
        }

        for (auto&& cf_desc : cf_descriptors)
        {
            rdb::ColumnFamilyHandle* cf_handle;
            db_temp->CreateColumnFamily(cf_desc.options, cf_desc.name, &cf_handle);

            cf_handles.push_back(cf_handle);
        }
    }

    db.reset(db_temp);
    for (auto&& cf_ptr : cf_handles)
    {
        cf_safe_handles.emplace_back(cf_ptr);
    }
}
// Operations
std::string value;
auto s = db->Put(rdb::WriteOptions(), cf_safe_handles[0].get(), "key1", "value1");
{
    std::string obj("phenomenal but will be removed shortly");

    s = db->Put(rdb::WriteOptions(), cf_safe_handles[1].get(), "key2", obj);
}
s = db->Get(rdb::ReadOptions(), cf_safe_handles[0].get(), "key1", &value);
assert(s.ok());
std::cout << " key1 := " << value << std::endl;

s = db->Get(rdb::ReadOptions(), cf_safe_handles[1].get(), "key2", &value);
std::cout << " key2 := " << value << std::endl;

assert(s.ok());

};

问题根源

  1. 变量名错误:代码中定义了cf_key_prefix_options,但后续配置table_factory和prefix_extractor时误用了未定义的cf_pwo_edge_options,导致目标列族的前缀提取器根本没被正确设置。
  2. 列族选项不匹配:RocksDB要求打开数据库时传入的列族选项必须与创建列族时完全一致,包括prefix_extractor、table_factory等核心配置。第二次运行时,即使变量名正确,若打开时的选项与创建时不一致,也会加载失败。
  3. 错误处理逻辑漏洞:首次打开失败后直接以无列族方式打开数据库再创建列族,但第二次运行时,列族已存在,此时调用CreateColumnFamily会报错,而代码未检查该操作的返回状态,导致后续逻辑异常。

修复后的代码

#include <cassert>
#include <cstdint>
#include <iostream>
#include <memory>
#include "rocksdb/db.h"
#include "rocksdb/options.h"
#include "rocksdb/slice_transform.h"
#include "rocksdb/table.h"

namespace rdb = rocksdb;

// 封装列族选项创建逻辑,确保创建和打开时选项一致
std::vector<rdb::ColumnFamilyDescriptor> GetColumnFamilyDescriptors() {
    std::vector<rdb::ColumnFamilyDescriptor> descriptors;

    // 普通列族
    rdb::ColumnFamilyOptions normal_cf_opts;
    descriptors.emplace_back("normal_cf", normal_cf_opts);

    // 带前缀提取器的列族
    rdb::ColumnFamilyOptions prefix_cf_opts;
    rdb::BlockBasedTableOptions block_opts;
    block_opts.block_size = 1ULL << 17;
    block_opts.enable_index_compression = false;
    block_opts.whole_key_filtering = false;
    prefix_cf_opts.table_factory.reset(rdb::NewBlockBasedTableFactory(block_opts));
    prefix_cf_opts.prefix_extractor.reset(rdb::NewFixedPrefixTransform(sizeof(std::uint64_t)));
    prefix_cf_opts.whole_key_filtering = false;
    descriptors.emplace_back("key_prefix_cf", prefix_cf_opts);

    return descriptors;
}

int main() {
    rdb::Options options;
    options.write_buffer_size = (1U << 28);
    options.max_open_files = (1U << 10);
    options.create_if_missing = true;
    options.wal_bytes_per_sync = 32768;
    options.bytes_per_sync = 32768;
    options.recycle_log_file_num = 8;

    // 全局表工厂配置(列族未指定时使用)
    rdb::BlockBasedTableOptions global_block_opts;
    global_block_opts.block_size = 1ULL << 17;
    global_block_opts.enable_index_compression = false;
    options.table_factory.reset(rdb::NewBlockBasedTableFactory(global_block_opts));
    options.whole_key_filtering = false;

    auto cf_descriptors = GetColumnFamilyDescriptors();
    std::unique_ptr<rdb::DB> db;
    std::vector<std::unique_ptr<rdb::ColumnFamilyHandle>> cf_handles;

    rdb::DB* db_temp = nullptr;
    std::vector<rdb::ColumnFamilyHandle*> raw_handles;
    auto status = rdb::DB::Open(options, "/tmp/test_cf", cf_descriptors, &raw_handles, &db_temp);

    if (!status.ok()) {
        // 尝试打开默认列族,再创建缺失的列族
        status = rdb::DB::Open(options, "/tmp/test_cf", &db_temp);
        if (!status.ok()) {
            std::cerr << "Failed to open database: " << status.ToString() << std::endl;
            return 1;
        }

        // 创建列族,逐个检查状态
        for (const auto& desc : cf_descriptors) {
            rdb::ColumnFamilyHandle* handle = nullptr;
            status = db_temp->CreateColumnFamily(desc.options, desc.name, &handle);
            if (!status.ok()) {
                std::cerr << "Failed to create column family " << desc.name << ": " << status.ToString() << std::endl;
                // 清理已创建的handle
                for (auto h : raw_handles) {
                    delete h;
                }
                delete db_temp;
                return 1;
            }
            raw_handles.push_back(handle);
        }
    }

    db.reset(db_temp);
    for (auto* h : raw_handles) {
        cf_handles.emplace_back(h);
    }

    // 测试操作
    std::string value;
    auto status_put = db->Put(rdb::WriteOptions(), cf_handles[0].get(), "key1", "value1");
    assert(status_put.ok());

    std::string obj = "phenomenal but will be removed shortly";
    status_put = db->Put(rdb::WriteOptions(), cf_handles[1].get(), "key2", obj);
    assert(status_put.ok());

    auto status_get = db->Get(rdb::ReadOptions(), cf_handles[0].get(), "key1", &value);
    assert(status_get.ok());
    std::cout << " key1 := " << value << std::endl;

    status_get = db->Get(rdb::ReadOptions(), cf_handles[1].get(), "key2", &value);
    assert(status_get.ok());
    std::cout << " key2 := " << value << std::endl;

    return 0;
}

修复说明

  1. 统一列族选项创建:将列族描述符的创建封装到函数GetColumnFamilyDescriptors中,确保每次打开数据库时使用的选项与创建时完全一致。
  2. 修正变量名错误:将误用的cf_pwo_edge_options替换为正确的prefix_cf_opts,确保前缀提取器和表工厂配置被正确应用到目标列族。
  3. 完善错误处理:检查CreateColumnFamily的返回状态,避免列族已存在时的异常;同时在失败时清理已分配的资源,防止内存泄漏。
  4. 明确全局与列族选项优先级:列族级的table_factory和prefix_extractor会覆盖全局选项,确保目标列族使用独立的配置。

内容的提问来源于stack exchange,提问作者lurscher

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 19:04:53