FeatureDB 存储引擎

本文档介绍 FeatureDB 的产品定位、部署配置、数据导入、Python SDK 使用、fdb-cli 运维管理以及当前版本的功能与限制。

FeatureDB 是运行于 DolphinDB 中的特征存储与低延时读取引擎,适用于 AI/ML、量化研究和在线特征服务等场景,尤其适合批量导入、频繁读取的工作负载。

FeatureDB 专注于在线特征数据的存储与访问,不提供通用 SQL 查询与分析能力。本手册将围绕当前版本支持的主要功能展开介绍,包括表管理、Parquet 数据导入、低延时读取等。

提示:FeatureDB 当前处于早期版本阶段,FeatureDB 当前处于早期版本阶段,

FeatureDB 运行于 DolphinDB 环境,并通过独立的 RPC 服务提供数据访问能力。其核心目标是为 AI/ML、量化、在线特征服务等场景提供低延时的数据访问能力,尤其适合批量导入、频繁读取的工作负载。

可以通过以下两种方式使用 FeatureDB:

  • 使用 fdb-cli 创建和管理表,并批量导入数据;

  • 使用 Python SDK(fdbpy)读取数据并开发业务应用。

1. FeatureDB 架构图

后续章节将分别介绍 FeatureDB 的部署配置、Parquet 数据导入、fdb-cli 运维管理和 Python SDK 使用方法。

本章按照从部署到导入数据的顺序,介绍完成一次 FeatureDB 试用所需的最小流程。你将完成以下操作:

  • 部署 FeatureDB 服务端并安装客户端工具;

  • 配置服务端和客户端连接参数;

  • 使用 fdb-cli 创建表;

  • 导入 Parquet 数据并检查导入任务状态。

fdb-cli 是随 Python wheel 一起安装的命令行工具。它本质上是 FeatureDB 服务端的文本命令客户端,适合运维管理、建表、导入和任务监控。

Python SDK 提供统一的 FeatureDB 访问能力,支持连接管理、通过文本命令进行表管理和数据导入,以及发布包提供的低延时读取接口。

当前版本能力边界

当前版本支持以下功能:

  • 服务端仅支持内存模式(持久化模式将在后续版本提供);

  • 通过标准 Parquet 文件批量导入数据;

  • 使用 Python SDK 进行低延时数据读取;

  • IPC、Socket 和 RDMA 三种通信后端。

当前版本不支持通用 SQL 查询与数据写入操作(如 SELECT、INSERT、UPDATE 和 DELETE)。

如需完整的 SQL 查询与分析能力,请使用 DolphinDB 标准数据库引擎。

适用场景

FeatureDB 适用于以下典型场景:

  • 在线推理服务读取特征矩阵;

  • 按时间点或时间范围读取时序特征;

  • 对指定特征列进行连续窗口读取;

  • 离线生成 Parquet 文件后批量导入,再供在线服务低延时读取。

部署与安装

FeatureDB 运行于 DolphinDB 服务端内置的功能模块,随安装包一同提供。使用前,需要在服务端配置文件中启用 FeatureDB RPC 服务,并在客户端安装随 Python wheel 提供的 Python SDK(fdbpy)和命令行工具(fdb-cli)。

目前 FeatureDB 支持部署于 Linux 系统的单机节点上。典型部署方式如下:

  • DolphinDB 部署于服务端主机,并动态加载 FeatureDB 模块;

  • 客户端既可以部署于服务端所在主机,也可以部署于远端主机;

  • 客户端与服务端位于同一主机时,可通过 IPC 建立连接;位于不同主机时,可通过 Socket 或 RDMA 建立连接。

服务端采用免安装设计,解压即可运行,无需单独安装,因此本节仅介绍客户端安装。

当前发布的 Python wheel 仅支持 Python 3.13,请确保客户端已安装对应版本的 Python 环境。

创建 Python 虚拟环境并安装 Python wheel:

python3.13 -m venv ~/fdbpy-3.13
~/fdbpy-3.13/bin/python -m pip install --upgrade pip
~/fdbpy-3.13/bin/python -m pip install \
  --force-reinstall \
  /path/to/fdbpy-0.1.0-cp313-cp313-linux_x86_64.whl

安装完成后,可使用以下工具:

  • ~/fdbpy-3.13/bin/fdb-cli:命令行管理工具,用于建表、导入数据和查看任务状态;

  • fdbpy:Python SDK,用于业务程序读取 FeatureDB 数据。

配置

配置分为服务端配置和客户端连接配置。服务端配置用于启动 FeatureDB 服务;客户端连接配置主要指 fdb-cli 使用的 TOML 配置文件。Python SDK 不读取该 TOML 文件,连接参数需在代码中通过 Endpoint 对象指定,详见第 5.1 节。

服务端配置

启用 FeatureDB 服务前,必须配置 RPC endpoint。以 Socket 为例,在 DolphinDB 运行目录下的 dolphindb.cfg 中增加以下配置:

fdbBackend=socket
fdbRpcEndpoint=192.168.100.250:19090
fdbNumServerWorkers=1
fdbImportRoot=/data/featuredb-import

各配置项说明如下:

  • fdbBackend:客户端连接方式。fdbBackend 不同配置对应的 fdbRpcEndpoint 配置格式参考下文的提示。

  • fdbRpcEndpoint:FeatureDB 服务监听地址。需要根据实际环境进行配置。如果未配置 fdbRpcEndpoint,DolphinDB 将正常启动,但不会启动 FeatureDB 服务,因此无法通过 Python SDK 或其他客户端访问 FeatureDB。

  • fdbNumServerWorkers:服务线程数,正整数。建议与可用 CPU 核数匹配。

  • fdbImportRoot:可选配置,FeatureDB 读取 Parquet 文件的根目录。

提示:

  • 首次试用建议优先使用 Socket 方式,配置更简单;确认功能可用后,再根据性能要求切换到 RDMA。

  • 客户端连接服务端的方式与 Endpoint 配置格式:

fdbBackend fdbRpcEndpoint 格式 说明
ipc <base-path> 进程间通信(Inter-Process Communication)。适用于同机部署、测试环境
socket

<host>:<port>

port 必须不同于 DolphinDB server 的 port

基于 TCP 的网络通信。适用于跨机器部署、通用生产环境
rdma <host>:<port>:<device> 基于 RDMA 的高性能通信。适用于低延迟、高吞吐场景(如高性能计算/金融场景)

完成以上配置后,运行如下命令启动 DolphinDB 和 FeatureDB:

./dolphindb

运行如下命令检查监听状态(需根据实际情况调整端口号):

  • RDMA 监听状态

    rdma resource show cm_id | grep 19091
  • Socket 的 TCP 监听状态

    ss -ltnp | grep 19090

fdb-cli 连接配置

Socket 连接配置

fdb-cli 使用 TOML 文件保存连接参数。Python SDK 不使用该 TOML 配置文件,而是在代码中通过 IpcEndpointSocketEndpointRdmaEndpoint 指定连接参数。以下是 Socket 连接示例:

backend = "socket"
endpoint = "192.168.100.250:19090"
connect_timeout_ms = 1000
io_timeout_ms = 5000
tcp_no_delay = true

保存为 fdb-socket.toml 后,可以通过以下命令连接服务端:

~/fdbpy-3.13/bin/fdb-cli --config ./fdb-socket.toml

也可以通过环境变量指定默认配置文件:

export FDB_CLI_CONFIG=/path/to/fdb-socket.toml

如果未指定 --config 且未设置 FDB_CLI_CONFIG,fdb-cli 会依次读取 $XDG_CONFIG_HOME/fdb/client.toml~/.config/fdb/client.toml;如果默认配置文件也不存在,则默认连接本机 IPC:backend = "ipc"endpoint = "/var/tmp/fdb.ipc"

IPC 连接配置

backend = "ipc"
endpoint = "/tmp/fdb.ipc"

RDMA 连接配置

如果使用 RDMA,除服务端地址和端口外,还需要确认客户端所在机器的 RDMA 设备信息,例如设备名、端口号和 GID 索引。RDMA 适合对访问延时要求较高的生产环境;如果只是功能验证,建议先使用 Socket。

RDMA 连接配置示例:

backend = "rdma"
endpoint = "181.181.1.240:19091:mlx5_0"
ib_port = 1
gid_index = 0
queue_depth = 256
max_inline_bytes = 256

上例中 181.181.1.240:19091:mlx5_0 的含义为:

  • 181.181.1.240:FeatureDB 所在机器的 RDMA 网络 IP。

  • 19091:FeatureDB 端口。

  • mlx5_0:客户端机器上的本地 RDMA 设备名。

常用检查命令包括 ibv_devicesibv_devinfo -d <device>ibdev2netdevshow_gids。确认 RDMA 环境可用后,再切换到 RDMA 连接。

创建表

连接 fdb-cli 后,可以使用 create table 创建 FeatureDB 表。下面的示例创建一张特征表;后续快速开始和 fdb-cli 示例均沿用 user_features 表,以保持建表、导入和读取流程一致。其中 user_id 是实体标识(Entity ID),用于唯一标识特征所属的实体;dt 是时间列,用于记录特征对应的时间;f0 到 f249 是特征列。

create table user_features (
    user_id INT64,
    dt DATEMINUTE,
    f<0..249> FLOAT16,
    valid BOOL
)
partitioned by dt values (<28800000..28800002>)

建表后,可使用以下命令查看表是否创建成功:

show tables
describe user_features

建表时需要注意:

  • FeatureDB 的表结构在建表时确定,后续导入的数据需与表结构保持一致。

  • 当存在大量连续命名的特征列时,可使用 fp<1..250> 这样的范围写法简化建表语句。

导入数据

FeatureDB 使用标准 Parquet 文件进行批量导入。可以使用 PyArrow、Spark、DuckDB 等工具生成 Parquet 文件,只要文件 Schema 与目标表一致即可导入。

在 fdb-cli 中执行以下命令,将与 user_features 表结构一致的 Parquet 文件导入该表:

load data infile './user_features.parquet' into table user_features

导入任务采用异步执行方式。提交命令后,返回结果中会包含任务 ID,可使用以下命令查看任务状态:

describe task-123456-1 alive

除 fdb-cli 外,也可以在 Python SDK 中通过 client.execute_command(...) 发送同样的文本命令来导入数据。该方式适合在业务程序或自动化脚本中完成建表、导入和任务状态检查。

import fdbpy as fdb

endpoint = fdb.SocketEndpoint(host="192.168.100.250", port=19090)
client = fdb.connect(endpoint)

print(client.execute_command(
    "create table user_features ("
    "user_id INT64, dt DATEMINUTE, f<0..249> FLOAT16, valid BOOL) "
    "partitioned by dt values (<28800000..28800002>)"
))

result = client.execute_command(
    "load data infile './user_features.parquet' into table user_features"
)
print(result)

# 根据 load data 返回的 task_id 查询导入状态
print(client.execute_command("describe task-123456-1 alive"))

如果使用 RDMA 连接,可将 endpoint 替换为 fdb.RdmaEndpoint:

endpoint = fdb.RdmaEndpoint(
    control_host="181.181.1.240",
    control_port=19091,
    device_name="mlx5_0",
)
client = fdb.connect(endpoint)

需要注意,load data infile 中的文件路径仍由 FeatureDB 服务端解析;远程连接时,路径必须对服务端可见,而不是仅存在于 Python 客户端所在机器。

快速开始中仅列出最容易导致导入失败的检查项;完整 Parquet/Arrow 类型映射和文件生成要求请参考第 3 章。

导入机制概述

FeatureDB 使用标准 Apache Parquet 作为批量导入格式,不引入专有文件格式。可使用 PyArrow、Spark、DuckDB 等工具生成 Parquet 文件,只要文件 Schema 与目标表一致,即可直接导入。导入过程中,FeatureDB 会自动解析 Parquet 文件并加载到内存存储引擎,无需进行额外的数据转换。

导入要求

  • 目标表已存在,或可通过 client.load_table() 自动建表。

  • 导入文件的列名、列顺序及列类型必须与目标表定义严格一致。

  • 只支持定宽基础类型,不支持嵌套类型。

  • DATETIME 和 DATEMINUTE 需要通过字段元数据标识逻辑类型。

支持的类型

FeatureDB 在 Parquet 导入过程中使用 Apache Arrow 作为中间表示,因此需要将 Parquet/Arrow 类型映射为 FeatureDB 内部类型。下表同时列出 create table 可声明的 FeatureDB 类型及其对应的 Parquet/Arrow 导入类型;其中 Parquet/Arrow 类型或备注留空/标注不支持的,表示当前不支持通过 Parquet 导入。

FeatureDB 类型 语义 Parquet / Arrow 类型 备注
BOOL 布尔值 BOOLEAN 支持建表和 Parquet 导入
INT8 8 位有符号整数 INT8 支持建表和 Parquet 导入
UINT8 8 位无符号整数 UINT8 支持建表和 Parquet 导入
INT32 32 位有符号整数 INT32 支持建表和 Parquet 导入
INT64 64 位有符号整数 INT64 支持建表和 Parquet 导入
FLOAT16 半精度浮点数 HALF_FLOAT 支持建表和 Parquet 导入
BFLOAT16 bfloat16 浮点数 支持建表;当前不支持 Parquet 导入
FLOAT32 单精度浮点数 FLOAT 支持建表和 Parquet 导入
FLOAT64 双精度浮点数 DOUBLE 支持建表和 Parquet 导入
FLOAT8_E4M3 8 位浮点数,E4M3 格式 支持建表;当前不支持 Parquet 导入
FLOAT8_E5M2 8 位浮点数,E5M2 格式 支持建表;当前不支持 Parquet 导入
TIMESTAMP 纳秒时间戳 timestamp[ns] 支持建表和 Parquet 导入
DATETIME epoch 秒,底层为 INT32 INT32 支持建表和 Parquet 导入;需要设置字段元数据 fdb.logical_type=DATETIME
DATEMINUTE epoch 分钟,底层为 INT32 INT32 支持建表和 Parquet 导入;需要设置字段元数据 fdb.logical_type=DATEMINUTE
STRING 字符串 支持建表;当前不支持 Parquet 导入

上表中,对于 DATETIME 和 DATEMINUTE,需要在 Arrow 字段元数据中增加:

Key: fdb.logical_type
Value: DATETIME | DATEMINUTE

如果缺少这一元数据,INT32 列会被当作普通整型导入,而不是时间类型。下例展示了如何使用 PyArrow 生成 FeatureDB 能正确识别时间类型的 Parquet 文件。

import pyarrow as pa
import pyarrow.parquet as pq

dt_field = pa.field(
    "dt",
    pa.int32(),
    nullable=False,
    metadata={b"fdb.logical_type": b"DATETIME"},
)
dm_field = pa.field(
    "dm",
    pa.int32(),
    nullable=False,
    metadata={b"fdb.logical_type": b"DATEMINUTE"},
)
ts_field = pa.field("ts", pa.timestamp("ns"), nullable=False)

schema = pa.schema([dt_field, dm_field, ts_field])

table = pa.table(
    {
        "dt": [1717110880, 1717111000],
        "dm": [28618514, 28618516],
        "ts": [1717110880123456789, 1717111000987654321],
    },
    schema=schema,
)

pq.write_table(table, "temporal.parquet", row_group_size=64 * 1024)

注意:Parquet row group 大小建议不超过 64,000 行,这与当前导入批次大小一致。

连接配置

CLI 连接参数使用 TOML 文件配置,完整格式、默认查找路径和 Socket/IPC/RDMA 示例见 2.2.2 客户端连接配置。本节只补充 CLI 特有的配置入口。

使用方式与示例

本节介绍交互式 REPL、一次性执行、批处理文件以及端到端建表导入流程。以下示例默认已准备好连接配置文件 fdb-rdma.toml

交互式 REPL

fdb-cli --config ./fdb-rdma.toml

连接成功后进入提示符,可直接输入命令:

fdb> help
fdb> show tables
fdb> describe ticks
fdb> exit

一次性执行命令

一次性执行命令时,可以直接在 CLI 参数后追加 FeatureDB 命令;推荐使用 -- 分隔 CLI 参数和 FeatureDB 命令。

fdb-cli --config ./fdb-rdma.toml -- show tables
fdb-cli --config ./fdb-rdma.toml describe ticks
fdb-cli --config ./fdb-rdma.toml --monitor-interval-ms 1000 describe task-123456-1 alive
fdb-cli --print-config-example

批处理文件示例

批处理文件可用于初始化表、导入数据和检查结果。命令文件每行一条命令;空行和以 # 开头的行会被忽略;行尾使用未转义的反斜杠 \ 可以续行;遇到第一条失败命令时停止执行。文件中出现 exitquit 时正常结束;执行成功的 stopServer 也会结束批处理。

# setup.fdb
create table ticks (id INT32, ts TIMESTAMP, price FLOAT32) \
  partitioned by id values (1,2,3)
load data infile 'ticks.parquet' into table ticks
describe ticks
fdb-cli --config ./fdb-rdma.toml --file setup.fdb

导入路径包含空格的文件

load data infile 的文件路径必须使用单引号包围;如果路径中包含空格,也应写在同一个单引号字符串中。

load data infile 'demo parquet.parquet' into table ticks

完整端到端流程

下面的示例展示了从建表、导入、监控任务到查看表信息的完整流程。实际使用时需将 task-123456-1 替换为 load data 返回的任务 ID。

create table user_features (
  user_id INT64,
  dt DATEMINUTE,
  f<0..3> FLOAT32,
  valid BOOL
) partitioned by dt values (<28800000..28800002>)

load data infile 'user_features.parquet' into table user_features
describe task-123456-1 alive

show tables
describe user_features

常用命令

fdb-cli 提供一组常用命令,用于表管理、数据导入及任务监控。以下为常用操作:

命令 作用 示例
help 显示服务端当前支持的命令摘要 help
exit / quit 断开客户端连接并退出 REPL exit
show tables 列出表及摘要信息 show tables
describe <table> 查看表元数据、列列表和分区分布 describe user_features
create table 创建表,并以内联列定义创建或复用同名 schema create table ticks (id INT32, ts TIMESTAMP, price FLOAT32)
drop table <table> 删除表 drop table ticks
load data infile 异步导入 Parquet 文件 load data infile '/data/ticks.parquet' into table ticks
describe <task-id>describe <task-id> alive 查看或持续监控导入任务状态 describe task-123456-1 alive
listBdev 列出已注册的 bdev listBdev
addBdev <config-path> 从配置文件添加 bdev。路径按普通 token 解析,不能包含空白 addBdev /etc/fdb/bdev.json
removeBdev <bdev-name> 移除指定 bdev removeBdev nvme0
stopServer 请求正在连接的 FeatureDB 服务端优雅退出 stopServer
test 运行启动阶段存储预检;仅适用于 SPDK 初始化前的启动检查 test

需要注意,load data infile 中的文件路径由 FeatureDB 服务端解析;远程连接时,该路径必须对服务端可见。

此外,FeatureDB 在标准 CREATE TABLE 语法基础上,支持部分扩展语法,用于简化大规模特征表定义。

具体包括:

  • 支持使用范围表达式批量生成连续列名:例如 feat<1..4>,等价于 feat1、feat2、feat3、feat4。该写法适用于大规模特征列定义场景。

  • 支持在 partitioned by values 中使用范围表达式:例如 values (0,<2..4>,9),等价于 0,2,3,4,9。

  • 关键字大小写不敏感(如 CREATE TABLEcreate table 等价)。

  • 标识符(表名、列名)暂不支持使用引号(如 "trade"、"price")。

create table user_features (
  user_id INT64,
  dt DATEMINUTE,
  f<0..3> FLOAT32,
  valid BOOL
) partitioned by dt values (<28800000..28800002>)

以上文本命令也可以通过 Python SDK 的 client.execute_command(...) 执行;Python 侧的集中示例见第 5 章。

Temporal Reader CLI 命令

Temporal Reader 用于按时间列定位当前时间点的行集合,并在该集合内查询或读取范围。

命令 作用 示例
temporalReaderCreate <reader-id> <temporal-column> <temporal-value> [table <table-name>] [id <id-value>] 创建或覆盖一个 reader,并定位到指定时间值。table 可限制到指定表,id 可在初始化时按 id 过滤;两者顺序可以互换。 temporalReaderCreate r1 dt 28800000 table user_features id 42
temporalReaderMove <reader-id> <delta-minutes> 按分钟移动 reader 的当前时间点,delta-minutes 可以为负数。

temporalReaderMove r1 -5

temporalReaderMove r1 10

temporalReaderSeek <reader-id> <temporal-value> 将 reader 定位到指定时间值。 temporalReaderSeek r1 28800001
temporalReaderLookup <reader-id> <id-value> 在 reader 当前 scope 内按 id 查找一行。如果当前 scope 中有多行匹配该 id,服务端会随机选择其中一行读取。 temporalReaderLookup r1 1001
temporalReaderReadScope <reader-id> [begin] [count] 读取当前 scope 的行引用列表。不带参数时读取整个 scope;只带 begin 时从该位置读到末尾;同时提供 begin 和 count 时读取半开区间 [begin, begin + count)。begin 和 count 必须是非负整数。

temporalReaderReadScope r1

temporalReaderReadScope r1 0 20

temporalReaderClose <reader-id> 关闭并移除 reader。 temporalReaderClose r1

Temporal Reader 命令的输出通常包含 reader_id、found、scope_size、previous_temporal、current_temporal,或 lookup/scope 行引用信息。输出面向终端阅读,不建议作为稳定机器解析协议。

temporalReaderCreate r1 dt 28800000 table user_features
temporalReaderLookup r1 1001
temporalReaderMove r1 1
temporalReaderReadScope r1 0 20
temporalReaderClose r1

常见命令输出为面向终端阅读的文本,不是稳定的机器解析协议。如需程序化读取数据,应优先使用 Python SDK 或 native transport API。常见输出包括:

  • create table:返回 Created table: <table-name>,分区表会追加分区信息。

  • drop table:返回 Dropped table: <table-name>

  • show tables:每张表一行,包含 id、schema、blob、pages、rows、estimated_bytes 和列摘要。

  • describe <table-name>:返回表元数据、分区值/分区分布以及列列表。

  • load data infile ...:返回 Submitted load task: <task-id> table=<table-name> file='<file-path>'

  • describe <task-id>:返回导入任务状态,例如 RUNNING、COMPLETED 或 FAILED,并可能包含文件路径和错误信息。

连接与管理

Python SDK 支持三种连接方式:

  • IpcEndpoint:本地进程通信

  • SocketEndpoint:基于 TCP 的远程连接

  • RdmaEndpoint:基于 RDMA 的高性能连接

import fdbpy as fdb

endpoint = fdb.SocketEndpoint(host="192.168.100.250", port=19090)
client = fdb.connect(endpoint)

Python SDK 支持执行与 CLI 等价的文本命令,用于表管理与元数据查询。

print(client.execute_command("show tables"))
print(client.execute_command("describe user_features"))

自动建表并导入

Python SDK 支持直接从 Parquet 文件导入数据。

client.load_table("user_features", "/data/user_features.parquet")

该接口会读取 Parquet schema 并自动创建匹配表,然后导入数据。注意路径是在 FeatureDB 服务器所在主机上解析的,因此远端部署时必须保证该路径对服务端可见。

数据读取

当前文档仅保留已验证或需要用户重点关注的读取入口。

f0_batch = reader.read_column_window_view(column="f0", begin=1000, length=128)
f0 = np.from_dlpack(f0_batch.features).reshape(-1)

或直接按表读取单列窗口:

time_batch = client.read_column_window(
    table="user_features", column="time", begin=1000, length=128
)
times = np.from_dlpack(time_batch.features).reshape(-1)

常见问题

为什么导入失败并提示 schema 不匹配?

通常是因为 Parquet 文件与目标表的列名、列顺序或类型不完全一致。需要逐列核对,特别是时间类型元数据和是否存在 null。

为什么 Python 可以连接但 load_table() 失败?

常见原因是 Parquet 路径是在服务端主机上解析的。如果服务端与客户端不在同一台机器上,客户端本地路径不一定对服务端可见。

什么时候用 CLI,什么时候用 Python SDK?

CLI 适合建表、导入、任务监控和人工排查;Python SDK 适合正式业务读取、自动化接入和低延时访问。

FeatureDB 是否支持普通 SQL 查询?

当前不支持通用 SELECTINSERTUPDATEDELETE。文本命令仅支持有限的操作子集,读取能力主要通过 Python SDK 提供。