在 Dataform,我们使用 Google Datastore 来存储客户数据。但是,由于各种原因,我们需要从 Datastore 转移到自我管理的数据库上。

我们以 protobuf 格式存储所有数据;我们存储的每个实体都对应一个 protobuf 消息。由于我们已经存储了结构化文档(相对于 SQL 表行),MongoDB 非常适合我们。

这是一个 protobuf 定义的简单示例:

message Person {
  string first_name = 1;
  string last_name = 2;
  int64 birth_timestamp_millis = 3;
}

使用协议缓冲区作为存储格式的主要好处之一是可以很容易地更改我们的数据库“模式”。重命名字段就像编辑.proto文件一样简单,并且(通常,有一些警告)更改字段的类型等是安全的,而在传统的类似 SQL 的表中重命名“字段”(列)通常很多的工作,涉及一定数量的数据库迁移。

但是,安全地更改 protobuf 定义需要将静态数据实际存储为 protobuf 格式,这将导致无法查询,因为数据库引擎不使用 protobuf。

解决此问题的一种方法是仅以规范的 JSON 格式存储消息。但是,我们将失去对 protobuf 定义进行多种更改的能力。例如,我们永远无法(轻松)重命名字段:假设我们以 JSON 格式存储了一个Person(如上定义)的实例,但随后将birth_timestamp_millis重命名为birthday_timestamp- 之前存储的Person现在将有一个不可解码的birthTimestampMillis字段,并且会缺少birthdayTimestamp的值。

我们真正想要的是两全其美:我们希望能够将消息存储为 JSON,以便轻松查询数据;但是我们希望存储的数据与我们可能想要对 protobuf 定义进行的各种向后/向前兼容的更改无关。

幸运的是,MongoDB 客户端库包含一个非常有用的功能:它们允许用户使用自定义的用户定义的编解码器定义数据在从数据库存储/检索时如何编码/解码。

我们已经使用这个特性定义了我们自己的新编解码器,用 Go 编写,它为我们解决了 protobuf 存储问题。它使用标记号作为文档键对 protobuf 消息进行编码,并对每个 protobuf 字段值使用标准编码/解码。

例如,给定以下 protobuf 定义:

message Example {
  string string_field = 3;
  ExampleEnum enum_field = 10;
  oneof example_oneof {
    int32 int32_field = 78;
    int64 int64_field = 33;
  }
  NestedMessage nested_message = 107;
}

enum ExampleEnum {
  VAL_0 = 1;
  VAL_1 = 573;
}

message NestedMessage {
  string nested_string_field = 2;
  int32 nested_int32_field = 1;
}

以下是Example的实例,采用规范的 JSON 格式:

{
  "stringField": "foo",
  "enumField": "VAL_0",
  // Note that this is represented as a string because the JavaScript number type is smaller than an int64.
  "int64Field": "123456789",
  "nestedMessage": {
    "nestedStringField": "bar",
    "nestedInt32Field": 12
  }
}

我们的 MongoDB 编解码器会将Example的实例编码为以下 Mongo BSON 文档:

{
  "3": "foo",
  "10": 1,
  "33": 123456789,
  "107": {
    "2": "bar",
    "1": 12
  }
}

使用这种编码,如果我们将名称nested_string_field更改为something_else,或者将枚举值VAL_0更改为BETTER_ENUM_VALUE_NAME,我们仍然能够解码文档,而不会丢失任何数据。

这确实使查询数据库变得更加困难,因为我们现在需要指定字段编号而不是人类可读的字段名称。但是,对于生产用途,我们在 MongoDB 前面放置了一个 gRPC 服务器,它知道如何构造正确的 MongoDB 查询,而对于临时查询,我们计划编写一个小型翻译器,它可以在给定包含 protobuf 字段名称的查询时执行相同的操作.

代码是开源的这里(godoc)。如何在 MongoDB 编解码器注册表中使用它的示例在测试中。如果对您有帮助,请随时使用!

Logo

MongoDB社区为您提供最前沿的新闻资讯和知识内容

更多推荐