news 2026/9/17 6:55:44

Feast 远程离线存储:用 Arrow Flight(gRPC)把离线存储服务化,实现跨网络的历史特征检索

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Feast 远程离线存储:用 Arrow Flight(gRPC)把离线存储服务化,实现跨网络的历史特征检索

Feast 远程离线存储:用 Arrow Flight(gRPC)把离线存储服务化,实现跨网络的历史特征检索

【免费下载链接】feastThe Open Source Feature Store for AI/ML项目地址: https://gitcode.com/GitHub_Trending/fe/feast

本篇基于 Feast 仓库中的examples/remote-offline-store官方示例,讲解如何将离线存储(Offline Store)部署为独立的 Arrow Flight 服务端,并在客户端以type: remote的离线存储配置通过网络完成训练数据集(点时连接特征)的拉取。读完后你将掌握:feast serve_offline服务端的启动与配置、客户端feature_store.yaml的完整参数含义,以及RemoteOfflineStore在源码层面如何通过 gRPC 的do_put/do_get协议将取数请求委托给远端服务。

一、为什么需要 Remote Offline Store

Feast 的离线存储(BigQuery、Snowflake、DuckDB 等)通常运行在训练环境所在的进程内:FeatureStore.get_historical_features()会直接在本地通过对应离线存储的实现执行点时连接(point-in-time join)。但在生产拓扑中,训练任务往往无法(或不应该)直连离线数仓,例如网络隔离、凭据收敛、多团队共享同一套特征资产等场景。

Feast 为此提供了 Remote Offline Store:它本质上是一个Apache Arrow Flight 服务端 + 客户端。服务端(Offline feature server)在拥有真实离线数据的地方运行,把OfflineStore接口的能力暴露为 Arrow Flight(gRPC)端点;客户端只需在feature_store.yaml中把offline_store.type设为remote并填写host/port,即可让所有离线取数请求——包括get_historical_featureswrite_logged_featuresoffline_write_batch等——被委托到远端执行。官方参考文档见 remote-offline-store.md,服务端文档见 offline-feature-server.md。

整个示例的目录结构为:

  • offline_server:一个完整的示例 Feast 仓库,包含特征定义与本地数据,作为远程服务端的部署对象;
  • offline_client:一个极简客户端,其feature_store.yaml使用remote类型离线存储,通过 test.py 验证历史特征拉取。

二、服务端准备:一个可被“远程化”的 Feast 项目

2.1 服务端 feature_store.yaml

示例服务端仓库 feature_store.yaml 的配置如下:

project: offline_server # By default, the registry is a file (but can be turned into a more scalable SQL-backed registry) registry: data/registry.db # The provider primarily specifies default offline / online stores & storing the registry in a given cloud provider: local online_store: type: sqlite path: data/online_store.db entity_key_serialization_version: 3

要点说明:

  • project: offline_server:项目名,客户端的同一份仓库配置必须与之保持一致(本例中客户端配置即声明project: offline_server);
  • registry: data/registry.db:默认使用本地文件型 registry,注释中也提示可替换为更可扩展的 SQL 后端 registry;
  • provider: local:本地 Provider,离线/在线存储默认使用文件与 SQLite;
  • entity_key_serialization_version: 3:实体键序列化版本,客户端与服务端应保持一致,否则反序列化实体键可能出错(仓库中专门有 entity-reserialization-of-from-v2-to-v3.md 讲解 v2→v3 迁移);
  • 注意该示例中没有显式声明offline_store:在provider: local下,离线数据由文件源(parquet)直接支撑,服务端的离线取数逻辑即围绕这些数据源展开。

2.2 特征定义 example_repo.py

example_repo.py 定义了客户端将要跨网络拉取的特征,核心结构如下:

# 实体:driver_id 作为主键 driver = Entity(name="driver", join_keys=["driver_id"]) # 从 parquet 文件读取数据 driver_stats_source = FileSource( name="driver_hourly_stats_source", path=f"{os.path.dirname(os.path.abspath(__file__))}/data/driver_stats.parquet", timestamp_field="event_timestamp", created_timestamp_column="created", ) # 特征视图:3 个特征字段,开启在线存储 driver_stats_fv = FeatureView( name="driver_hourly_stats", entities=[driver], ttl=timedelta(days=1), schema=[ Field(name="conv_rate", dtype=Float32), Field(name="acc_rate", dtype=Float32), Field(name="avg_daily_trips", dtype=Int64, description="Average daily trips"), ], online=True, source=driver_stats_source, tags={"team": "driver_performance"}, )

此外还定义了:

  • RequestSourcevals_to_add,字段val_to_add/val_to_add_2):只存在于请求时刻的输入数据;
  • @on_demand_feature_view装饰的transformed_conv_rate:在conv_rate基础上加上请求字段,产出conv_rate_plus_val1conv_rate_plus_val2
  • 基于PushSourcedriver_hourly_stats_fresh视图及driver_activity_v1/v2/v3三个 FeatureService。

客户端后面要跨网络拉取的正是driver_hourly_stats的三个基础特征与transformed_conv_rate的两个按需变换特征。数据文件为 driver_stats.parquet,在线数据落在data/online_store.db(SQLite)。

三、启动远程离线服务端

3.1 应用特征仓库并启动服务

在服务端目录下先执行 apply 注册特征与数据源,再启动离线服务(引自 README 的原始步骤):

cd offline_server feast -c feature_repo apply
feast -c feature_repo serve_offline

启动成功的样例输出:

Serving on grpc+tcp://127.0.0.1:8815

feast serve_offline会拉起一个 Arrow Flight 服务,默认监听127.0.0.1:8815。从源码看,该命令定义在 serve.py,可用参数为:

参数缩写默认值说明
--host-h127.0.0.1服务监听主机
--port-p8815服务端口,常量定义于 constants.py(DEFAULT_OFFLINE_SERVER_PORT = 8815
--key-kTLS 私钥证书路径;需与--cert同时提供才能以 TLS 模式启动
--cert-cTLS 公钥证书路径;只传其中一个会报BadParameter

serve_offline_command最终调用store.serve_offline(host, port, tls_key_path, tls_cert_path),该方法在 feature_store.py 中转发到offline_server.start_server(...)完成 gRPC 服务注册与启动。

3.2 以环境变量注入 feature_store.yaml(容器化部署)

示例 README 还描述了服务端的另一种初始化方式:通过名为FEATURE_STORE_YAML_BASE64的环境变量提供feature_store.yaml文件(Base64 编码)。服务端会创建一个临时目录,并把该 YAML 解包为其中的feature_store.yml再加载。该环境变量名在源码中的常量为 constants.py 的FEATURE_STORE_YAML_ENV_NAME = "FEATURE_STORE_YAML_BASE64"。这种方式适合把服务端打成镜像后以 K8s Pod 运行,无需在镜像内挂载仓库目录。

四、客户端配置:offline_store type: remote

4.1 客户端 feature_store.yaml

客户端仓库 feature_store.yaml 的完整内容为:

project: offline_server # By default, the registry is a file (but can be turned into a more scalable SQL-backed registry) registry: ../offline_server/feature_repo/data/registry.db # The provider primarily specifies default offline / online stores & storing the registry in a given cloud provider: local offline_store: type: remote host: localhost port: 8815 entity_key_serialization_version: 3

关键设计:

  • project与服务端一致,指向同一个offline_server项目的元数据;
  • registry直接复用服务端 apply 后生成的本地 registry 文件(../offline_server/feature_repo/data/registry.db)。也就是说,registry 是客户端本地可见的,而真正的数据查询被委托给远端——这是理解 Remote Offline Store 工作方式的核心:客户端持有“元数据”,服务端持有“数据”;
  • offline_store段声明委托配置:type: remote且给出hostport

4.2 配置项全解:RemoteOfflineStoreConfig

type: remote对应的 Pydantic 配置模型是 remote.py 中的RemoteOfflineStoreConfig,可配置字段比示例中用到的更多:

class RemoteOfflineStoreConfig(FeastConfigBaseModel): type: Literal["remote"] = "remote" scheme: Literal["http", "https"] = "http" # https 时以 grpc+tls 连接 host: StrictStr # 必填:Arrow Flight 服务端地址 port: Optional[StrictInt] = None # 服务端端口 cert: StrictStr = "" """ 服务端以 TLS 模式启动(例如自签名证书)时,客户端需要指向公钥证书 文件(通常以 .crt/.cer/.pem 结尾)。""" connection_retries: int = Field(default=3, ge=0) """ 针对瞬时 Arrow Flight 错误的重试次数,指数退避(默认 3)。"""

各字段的作用(结合 build_arrow_flight_client 的实现):

  • host/port:拼出 Arrow Flight 连接串。默认scheme: http对应grpc+tcp://host:port;当scheme: https时切换为grpc+tls,即服务端以 TLS 启动时客户端必须同步声明https并提供cert
  • cert:以二进制读取后作为tls_root_certs传入 Flight 客户端,用于信任自签证书;
  • connection_retries(默认 3,允许 0):客户端类FeastFlightClient继承pyarrow.flight.FlightClient,并叠加arrow_client_error_handling_decoratorget_flight_infodo_getdo_put等调用做错误处理与重试包装,见 remote.py。

五、客户端执行历史特征拉取

test.py 构造了一个包含driver_idevent_timestamplabel_driver_reported_satisfactionval_to_addval_to_add_2的实体 DataFrame,然后像使用任何本地 Feast 客户端一样拉取历史特征:

from datetime import datetime from feast import FeatureStore import pandas as pd entity_df = pd.DataFrame.from_dict( { "driver_id": [1001, 1002, 1003], "event_timestamp": [ datetime(2021, 4, 12, 10, 59, 42), datetime(2021, 4, 12, 8, 12, 10), datetime(2021, 4, 12, 16, 40, 26), ], "label_driver_reported_satisfaction": [1, 5, 3], "val_to_add": [1, 2, 3], "val_to_add_2": [10, 20, 30], } ) features = [ "driver_hourly_stats:conv_rate", "driver_hourly_stats:acc_rate", "driver_hourly_stats:avg_daily_trips", "transformed_conv_rate:conv_rate_plus_val1", "transformed_conv_rate:conv_rate_plus_val2", ] store = FeatureStore(repo_path=".") training_df = store.get_historical_features(entity_df, features).to_df()

运行方式(README 原始步骤):

cd offline_client python test.py

样例输出节选(完整输出见 README):

config.offline_store is <class 'feast.infra.offline_stores.remote.RemoteOfflineStoreConfig'> ----- Feature schema ----- <class 'pandas.core.frame.DataFrame'> RangeIndex: 3 entries, 0 to 2 Data columns (total 10 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 driver_id 3 non-null int64 1 event_timestamp 3 non-null datetime64[ns, UTC] 2 label_driver_reported_satisfaction 3 non-null int64 3 val_to_add 3 non-null int64 4 val_to_add_2 3 non-null int64 5 conv_rate 3 non-null float32 6 acc_rate 3 non-null float32 7 avg_daily_trips 3 non-null int32 8 conv_rate_plus_val1 3 non-null float64 9 conv_rate_plus_val2 3 non-null float64 dtypes: datetime64ns, UTC, float32(2), float64(2), int32(1), int64(4) memory usage: 332.0 bytes None ----- Features ----- driver_id event_timestamp label_driver_reported_satisfaction ... avg_daily_trips conv_rate_plus_val1 conv_rate_plus_val2 0 1001 2021-04-12 10:59:42+00:00 1 ... 590 1.022378 10.022378 1 1002 2021-04-12 08:12:10+00:00 5 ... 974 2.762213 20.762213 2 1003 2021-04-12 16:40:26+00:00 3 ... 127 3.419828 30.419828 [3 rows x 10 columns]

第一行输出config.offline_store is <class 'feast.infra.offline_stores.remote.RemoteOfflineStoreConfig'>证明了客户端确实在使用 remote 类型的离线存储配置;返回的 10 列中既有点时连接得到的driver_hourly_stats基础特征,也包含transformed_conv_rate按需特征视图的计算结果,与实体表字段共同构成可直接用于模型训练的训练集。

六、源码解析:RemoteOfflineStore 如何把请求委托到远端

客户端类的实现在 remote.py,它实现了标准OfflineStore接口的远端版本。从源码结构看,其通信协议是一个统一的“两步式”命令模型:

第一步:_call_put上传命令描述与输入数据。_call_put 为每次调用生成一个 UUID 作为command_id,把api(远端应执行的方法名)与参数打包成 JSON 构造FlightDescriptor.for_command(...),随后通过 _put_parameters 用client.do_put上传 Arrow 数据:entity_df(pandas DataFrame,经pa.Table.from_pandas转换)、table(已是 Arrow 表的数据),或在两者都缺省时上传一个仅含key列的占位表。

第二步:_call_get取回结果。_call_get 先用client.get_flight_info(command_descriptor)拿到 flight 信息与 ticket,再client.do_get(ticket)读取流式结果并read_all()为 Arrow 表。检索类方法经由 _send_retrieve_remote 串联这两步。

在此协议上,RemoteOfflineStore覆盖了以下能力(与 remote-offline-store.md 文档描述的能力清单一致,实际覆盖方法更多):

方法远端执行的语义委托方式
get_historical_features点时连接,构建训练集返回RemoteRetrievalJobto_df()/to_arrow()时才真正发起 put+get;start_date/end_date以 ISO 字符串序列化传输,entity_df若为 SQL 字符串则放入entity_df_sql参数
pull_all_from_table_or_query从数据源全量拉取put+get
pull_latest_from_table_or_query拉取每个实体最新一条put+get
write_logged_features写回推理日志特征do_putfeature_service_name作为参数)
offline_write_batch批量写入特征视图数据do_put
validate_data_source / get_table_column_names_and_types_from_data_sourceapply 阶段的数据源校验与列 schema 探测前者 put、后者 put+get

其中get_historical_features是懒执行(lazy)的典型:它只是把feature_view_namesfeature_refsprojectfull_feature_namesname_aliases等参数封进RemoteRetrievalJob,真正的网络交互发生在调用to_df()/to_arrow()时——_to_arrow_internal触发_send_retrieve_remote,结果 Arrow 表再to_pandas()回到 pandas。RemoteRetrievalJob.persist甚至复用了同一协议把“把查询结果落地为 SavedDataset”这一动作也放到远端执行(remote.py)。

在安全方面,build_arrow_flight_client检查仓库配置中的auth_config:当认证类型不是AuthType.NONE时,会通过FlightAuthInterceptorFactory给客户端挂上认证拦截器(remote.py),使每次 Flight 调用携带鉴权信息;认证与授权的完整配置可参考 permission.md。

七、生产化:Kubernetes 部署与权限模型

在集群中,服务端可通过 Feast Operator 的 FeatureStore CR 直接声明 offlineStore 服务(见 offline-feature-server.md):

apiVersion: feast.dev/v1 kind: FeatureStore metadata: name: sample-offline-server spec: feastProject: my_project services: offlineStore: server: {}

更多 FeatureStore CR 写法可参考 infra/feast-operator/config/samples;K8s 部署的完整背景见 running-feast-in-production.md。

服务端暴露的每个端点都有对应的 RBAC 权限要求,来自 offline-feature-server.md 的权限矩阵:

端点资源类型权限说明
offline_write_batchFeatureViewWrite Offline向离线存储写批次数据
write_logged_featuresFeatureServiceWrite Offline写推理日志特征
persistDataSourceWrite Offline把读取结果持久化到离线存储
get_historical_featuresFeatureViewRead Offline检索历史特征
pull_all_from_table_or_queryDataSourceRead Offline全量拉取数据
pull_latest_from_table_or_queryDataSourceRead Offline拉取最新数据

八、小结与适用边界

  • 架构分工:客户端持有 registry 元数据与remote委托配置;服务端持有真实离线数据并以 Arrow Flight(gRPC)暴露OfflineStore接口。取数、写日志、批量写入、数据源校验全部走统一的 put(命令+数据)+ get(结果)协议,列式 Arrow 数据避免了二次序列化开销。
  • 最小可运行链路:服务端feast -c feature_repo apply && feast -c feature_repo serve_offline(默认127.0.0.1:8815,可用--host/--port/--key/--cert覆盖);客户端配置offline_store: {type: remote, host, port}后照常调用get_historical_features
  • 生产加固scheme: https+cert建立 TLS 通道,connection_retries控制瞬时错误重试(默认 3 次、指数退避),Operator 的 FeatureStore CR 声明式部署,RBAC 按端点粒度授权。
  • 适用前提:客户端与服务端必须指向同一project的 registry 元数据,且entity_key_serialization_version等序列化约定保持一致;remote 存储支持的功能集合与服务端 SDK 直连离线存储的能力一致(详见 offline-stores overview 中的功能矩阵)。

参考路径索引

  • 示例 README 与运行步骤:examples/remote-offline-store/README.md
  • 服务端仓库:offline_server/feature_repo/feature_store.yaml、offline_server/feature_repo/example_repo.py
  • 客户端仓库:offline_client/feature_store.yaml、offline_client/test.py
  • 客户端实现:sdk/python/feast/infra/offline_stores/remote.py
  • CLI 与常量:sdk/python/feast/cli/serve.py、sdk/python/feast/constants.py、sdk/python/feast/feature_store.py
  • 参考文档:docs/reference/offline-stores/remote-offline-store.md、docs/reference/feature-servers/offline-feature-server.md、docs/getting-started/concepts/permission.md

【免费下载链接】feastThe Open Source Feature Store for AI/ML项目地址: https://gitcode.com/GitHub_Trending/fe/feast

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 6:55:43

用gm/id方法高效设计折叠式共源共栅放大器全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/17 6:55:24

GD32H759+RT-Thread工控入门:从点灯到可信系统构建

1. 为什么选 GD32H759 RT-Thread 做工控入门&#xff1f;这不是凑热闹&#xff0c;是踩过坑后的理性选择GD32H759 这颗芯片刚发布时&#xff0c;我第一时间拿到样片&#xff0c;不是因为它是“国产最强”&#xff0c;而是因为它在工控场景里&#xff0c;把几个关键矛盾点真正理…

作者头像 李华
网站建设 2026/9/17 6:55:15

XZ6328高压LDO:宽压输入下的低噪声稳压方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/17 6:54:57

Java工程师落地大模型:SpringAI+PostgreSQL向量库实战指南

1. 这不是一本“理论书”&#xff0c;而是一份Java工程师落地大模型应用的实操地图如果你正坐在工位上&#xff0c;手边是刚搭好的SpringBoot 3.2项目&#xff0c;IDEA里弹着“Failed to resolve org.springframework.ai:spring-ai-openai-spring-boot-starter”的报错&#xf…

作者头像 李华
网站建设 2026/9/17 6:54:31

SpringBoot+Vue校园回忆录系统开发实践

1. 项目背景与核心价值在大学校园里&#xff0c;班级回忆录一直是连接同学情感的重要纽带。但传统的纸质相册和零散的电子文档存在三个致命问题&#xff1a;一是容易丢失损坏&#xff0c;二是难以多人协作编辑&#xff0c;三是检索效率低下。海滨学院班级回忆录系统正是为了解决…

作者头像 李华
网站建设 2026/9/17 6:53:18

国产MCU选型支持能力评估指南:FAE响应、文档、SDK与量产四维实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华