Skip to content

Migrate Complex Open DB Code

mytrygithub edited this page Jul 10, 2023 · 11 revisions

(一)背景

在很多场景下,使用 RocksDB 的老代码,其 Open DB 的代码非常复杂,甚至可能有一套管理 DB/ColumnFamily 的框架(例如 flink 的 rocksdb state backend)。

要把这样的项目迁移到 ToplingDB,并使用 SidePluginRepo 来配置和管理 DB/ColumnFamily 对象,需要的改动会比较多,所以,为了降低这种代码迁移成本,我们设计了一个新的方案。

(二)将打开的 DB 注入 SidePluginRepo

这个功能很久以前已经在 SidePluginRepo 有初步支持了,但是当初觉得这是一个鸡肋功能,并未进行深入的设计考虑,现在,我们对此进行了完善和测试。

2.1. 主要的接口( C++ )

class SidePluginRepo { // 省略无关的成员
public:
  // The caller should ensure DB handle's life time is longer than SidePluginRepo
  void Put(const std::string& name, DB*);
  void Put(const std::string& name, DB*, const std::vector<ColumnFamilyHandle*>&);
  void Put(const std::string& name, DB_MultiCF*);

  void Put(const std::string& name, const std::shared_ptr<Options>&);
  void Put(const std::string& name, const std::shared_ptr<DBOptions>&);
  void Put(const std::string& name, const std::shared_ptr<ColumnFamilyOptions>&);

  bool Get(const std::string& name, std::shared_ptr<Options>*) const;
  bool Get(const std::string& name, std::shared_ptr<DBOptions>*) const;
  bool Get(const std::string& name, std::shared_ptr<ColumnFamilyOptions>*) const;

  Auto Get(const std::string& name) const; // C++ trick: shared_ptr<DBOptions> dbo = repo["dbo"];
  Auto operator[](const std::string& name) const; // C++ trick
};

2.2. 主要的接口( Java )

class SidePluginRepo { // 省略无关的成员
    public void put(String name, RocksDB db);
    public void put(String name, String spec, Options opt);
    public void put(String name, String spec, DBOptions dbo);
    public void put(String name, String spec, ColumnFamilyOptions cfo);

    // will get a clone on each call, to sync, put after modified
    public ColumnFamilyOptions getCFOptions(String name);
    public DBOptions getDBOptions(String name);
}

(三)使用方法

3.1. C++ 代码示例

// begin new code 1
SidePluginRepo repo;
repo.ImportAutoFile(json_or_yaml_file1); // basic conf
shared_ptr<DBOptions> dbo = repo["dbo"];
shared_ptr<ColumnFamilyOptions> cfo = repo["cfo"];
// end new code 1

OldCodeUpdateDBO(dbo);
OldCodeUpdateCFO(cfo);

// maybe update "dbo" and "cfo", "basic conf" and "update conf" can be
// both present or just one of the two.
// here dbo/cfo are from old user code, we put them to repo
repo.Put("dbo", dbo);
repo.Put("cfo", cfo);
repo.ImportAutoFile(json_or_yaml_file2); // update conf

// begin old code, open db, omit error check
vector<ColumnFamilyOptions> cf_desc;
vector<ColumnFamilyHandle*> cf_handles;
Add_Many_cfo_to(cf_desc);
DB* db = nullptr;
std::string dbpath = FromSomeConf(); // DB::Open's name param is really path
DB::Open(dbpath, cf_desc, &cf_handles, &db);
// end old code

// begin new code 2
std::string dbname; // name, not path, new concept of SidePluginRepo
repo.Put(dbname, db, cf_handles);
repo.StartHttpServer();
// end new code 2

// old code ...

repo.CloseAllDB(false); // new code 3, close

一般情况,只需要这些修改,RocksDB 到 ToplingDB 的迁移就完成了。

其中,我们可以先从 json/yaml 文件导入 option,再用代码修改 option,然后再次用导入另一个 json/yaml 文件对 option 做最后的修改,这两次 ImportAutoFile,根据具体情况,可以缩减为一次。总的原则是:后面的覆盖前面的。

3.2. Java 代码示例

使用 Java,大部分与 C++ 几乎完全相同,最主要的不同是,Java 中 importAutoFile 之后必须重新用对象名向 repo 中 get 相应的对象引用:

repo.put("dbo", dbo);
repo.put("cfo", cfo);
repo.importAutoFile(json_or_yaml_file2); // update conf
dbo = repo.getDBOptions("dbo"); // update to dbo in java
cfo = repo.getCFOptions("cfo"); // update to cfo in java

另一个不同的地方是:

// C++ Code:
// shared_ptr<DBOptions> dbo = repo["dbo"];
// shared_ptr<ColumnFamilyOptions> cfo = repo["cfo"];

// Java Code:
DBOptions dbo = repo.getDBOptions("dbo");
ColumnFamilyOptions cfo = repo.getCFOptions("cfo");

完整示例:

// begin new code 1
SidePluginRepo repo = new SidePluginRepo();
repo.importAutoFile(json_or_yaml_file1); // basic conf
DBOptions dbo = repo.getDBOptions("dbo");
ColumnFamilyOptions cfo = repo.getCFOptions("cfo");
// end new code 1

oldCodeUpdateDBO(dbo);
oldCodeUpdateCFO(cfo);

// maybe update "dbo" and "cfo", "basic conf" and "update conf" can be
// both present or just one of the two.
// here dbo/cfo are from old user code, we put them to repo
repo.put("dbo", dbo);
repo.put("cfo", cfo);
repo.importAutoFile(json_or_yaml_file2); // update conf
dbo = repo.getDBOptions("dbo"); // update to dbo in java
cfo = repo.getCFOptions("cfo"); // update to cfo in java

// begin old code, open db, omit error check
List<ColumnFamilyOptions> cf_desc = new ArrayList<ColumnFamilyOptions>();
List<ColumnFamilyHandle> cf_handles = new ArrayList<ColumnFamilyHandle>();
// Add_Many_cfo_to(cf_desc); // weird, rocksdb jni cfname is byte[]
   cf_desc.add(new ColumnFamilyDescriptor("cfo".getBytes(), cfo));
   cf_desc.add(new ColumnFamilyDescriptor("more cfo".getBytes(), more_cfo));
String dbpath = FromSomeConf(); // DB::Open's name param is really path
RocksDB db = RocksDB.open(dbpath, cf_desc);
// end old code

// begin new code 2
String dbname; // name, not path, new concept of SidePluginRepo
repo.put(dbname, db, cf_handles);
repo.startHttpServer();
// end new code 2

// old code ...

repo.closeAllDB(); // new code 3, close

Java 代码也一样,两次 importAutoFile,根据具体情况,可以缩减为一次,主要看是用 Java 代码覆盖 json/yaml 中的同名配置项(DBOptions/ColumnFamilyOptions 的成员),还是用 json/yaml 覆盖 Java 代码中设定的同名配置项。总的原则是:后面的覆盖前面的。

【完】

Clone this wiki locally