拓冰建站拓冰建站
首页 / 资讯中心 / 正文

DORA 的 Arrow 主版本解耦设计:如何让 `dora-node-api` 在 1.x 生命周期内自由升级 Arrow

DORA 的 Arrow 主版本解耦设计如何让dora-node-api在 1.x 生命周期内自由升级 Arrow【免费下载链接】doraDORA (Dataflow-Oriented Robotic Architecture) is middleware designed to streamline and simplify the creation of AI-based robotic applications. It offers low latency, composable, and distributed dataflow capabilities. Applications are modeled as directed graphs, also referred to as pipelines.项目地址: https://gitcode.com/GitHub_Trending/do/dora导读本篇文章解析 DORADataflow-Oriented Robotic Architecture中一项已落地实施的设计将dora-node-api的公开 API 与 Apache Arrow 主版本解耦。Arrow 每 68 周发布一个主版本而 DORA 1.x 的公开 API 一经冻结即伴随整个大版本生命周期——如果 Arrow 类型直接出现在冻结契约中DORA 1.x 将被钉死在单一 Arrow 主版本上用户必须在最新 DORA与最新 polars / datafusion / parquet之间二选一。本文基于 docs/plan-arrow-version-decoupling.md 的设计文档结合仓库源码说明DoraArray新类型、arrow_vN特性化重导出、C Data Interface 跨版本桥接与支持窗口策略的实现细节。读完本文你将理解如何在冻结的公共 API 边界内安全地升级底层依赖主版本并掌握arrow-v58/arrow-v59特性在实际节点中的用法。问题冻结契约中的 Arrow 泄漏点DORA 1.0 承诺从dora-node-api公共 API 可达的一切在 1.x 生命周期内冻结。而 Arrow 恰好以三种方式渗透进这个冻结面位置内容apis/rust/node/src/lib.rspub use arrow;—— 整个 crate 被重导出libraries/arrow-convert/src/lib.rspub struct ArrowData(pub arrow::array::ArrayRef)—— 公共字段外加DerefTarget ArrayRef同上trait IntoArrow { type A: arrow::array::Array; }—— 关联类型约束其中最致命的一环是ArrowData它是Event::Input的公共字段见 apis/rust/node/src/event_stream/event.rs处于每个节点最热门的接收路径上。由于这些类型在契约内用户无法把自己所用主版本的 Arrow 与 DORA 的 Arrow 混用——Cargo 将不同主版本视为互不相关的类型send_output会拒绝接收用户构建的数组。需要澄清问题不在线上格式设计文档明确指出线格式不是问题。DORA 使用 Arrow IPC 序列化见 apis/rust/node/src/node/arrow_utils.rs而 IPC 是与 arrow-rs Rust API 无关的稳定格式——两个基于不同 Arrow 主版本构建的 DORA 节点之间已经可以正确交换数据。耦合纯粹发生在进程内的 Rust 类型边界上。备选方案的代价设计文档的对比分析备选方案代价在整个 1.x 期间钉死 Arrow一年期 1.x 结束时落后约 8 个主版本用户想要新 polars/datafusion/parquet 必须二选一在文档中把 Arrow 移出 semver 保证Arrow 生态自身parquet、arrow-flight就是这么做的但 DORA 的小版本将破坏构建用户被迫精确锁定版本让公开边界直接就是 C Data Interface每个节点在热路径上多一次可失败转换并失去Deref带来的便利让Event对 payload 泛型化把类型参数推进每个用户签名里最终选择的是四部分组合设计。设计第一部分DoraArray—— 签名中的 DORA 自有类型核心思想用 DORA 自有的新类型替换公共签名中的 Arrow 类型使冻结契约中不出现任何 Arrow 类型——这才是真正买到自由的一步有了它DORA 可以在小版本中升级内部 Arrow。具体改动ArrowData(pub ArrayRef)及其DerefTarget ArrayRef变为DoraArray字段私有。实现见 libraries/arrow-convert/src/lib.rs#[derive(Debug, Clone)] pub struct DoraArray(arrow::array::ArrayRef);IntoArrow::A: Array变为fn into_arrow(self) - DoraArray。特质故意没有关联类型——type A: arrow::array::Array的约束会把 Arrow 重新塞回冻结 API。见 libraries/arrow-convert/src/lib.rspub trait IntoArrow { /// Convert the data into a dora payload. fn into_arrow(self) - DoraArray; }Event::Input { data: DoraArray }见 apis/rust/node/src/event_stream/event.rs。DoraArray持有的是 DORA 的内部Arrow 版本因此处于同一主版本的用户保留今天的全部便利无需任何转换IntoArrow for DoraArray是恒等转换send_output既可以接收普通 Rust 值也可以接收已构建好的 payload。未加门的原始访问器必须被特性门控一个不加门控的DoraArray::as_array() - arrow::array::ArrayRef会把 Arrow 直接放回公共 API让整个工程前功尽弃。因此直接访问器放在 DORA当前内部主版本对应的特性后面#[cfg(feature arrow-v59)] impl DoraArray { pub fn as_array(self) - arrow59::array::ArrayRef { self.0 } pub fn into_inner(self) - arrow59::array::ArrayRef { self.0 } }实现见 libraries/arrow-convert/src/lib.rs。配合default []该访问器不在默认表面内。当 DORA 未来内部升级到 60 时直接访问器重新挂到arrow-v60门下而arrow-v59保留一个转换型访问器——旧代码仍然编译只是多付出一次 FFI 跳转。DoraArray还提供了不加门控的检查方法len()、is_empty()、null_count()、以及返回String而非arrow_schema::DataType的type_name()用于日志与报错见 libraries/arrow-convert/src/lib.rs。设计第二部分arrow_vN特性化重导出pub use arrow;是不诚实的DORA 升级内部主版本时它的含义会静默变化。而pub use arrow as arrow_v59;不可能如此——它要么存在且意味着 Arrow 59要么就明显消失。这把一次升级从变更变成了增量内部迁移到 60 只是新增arrow_v60arrow_v59继续工作。Cargo 配置的落地形态见 apis/rust/node/Cargo.toml 与 libraries/arrow-convert/Cargo.toml[dependencies] arrow { workspace true, features [ipc] } # internal, ungated arrow58 { package arrow, version 58, optional true } [features] default [] arrow-v58 [dep:arrow58] arrow-v59 [] # doras internal version; re-export onlyarrow-v59刻意不引入额外依赖它重导出的是已经存在的内部arrow因此整个构建中只有一份 Arrow 59 被链接。只有内部主版本之外的其他主版本才会获得带别名的optional依赖。特性的存在让指名内部主版本和指名其他主版本一样是显式 opt-in且当 59 不再是内部版本时这个门控结构可以原样保留。实际重导出的代码见 apis/rust/node/src/lib.rs#[cfg(feature arrow-v59)] pub use arrow as arrow_v59; #[cfg(feature arrow-v58)] pub use arrow58 as arrow_v58;两条必须遵守的规则规则一DORA 的内部 Arrow 保持不加门控。内部 IPC 编码/解码必须针对恰好一个版本编写否则每个调用点都需要cfg。只有额外的主版本才是可选的。当 DORA 未来内部迁移到 60 时此前的内部版本 59 降级为又一个可选的兼容特性。规则二default []。如果受祝福的版本坐在default里而default日后变化对任何依赖它的人来说就是破坏性变更——静默漂移问题在高一层重新上演。空的 default 永不漂移。这一点之所以可行正是因为有DoraArray节点写node.send_output(id, meta, 42u32.into_arrow())?时从不指名 Arrow 类型因此常见路径根本不需要任何特性。Cargo 特性是可加的且这里的特性是诚实的可加启用arrow-v58只增加一个重导出和若干From实现。因此若依赖图中一个 crate 要arrow-v58、另一个要arrow-v60Cargo 会统一启用两者各取所需。两个 Arrow 主版本可以共存不同的 crate、不同的符号、纯 Rust无 C 符号冲突。此外Cargo 会统一 semver 兼容的版本——用户自己声明arrow 59得到的 crate 实例与dora_node_api::arrow_v59是同一个。类型可互换而非仅仅相似这正是用户在同一个主版本上混用 DORA 与 polars/parquet 的安全性基础。设计第三部分经 C Data Interface 的TryFrom/TryInto设计修正TryFrom而非From设计文档在此处有一处正式更正最初版本写的是From/Into这是错误的。C Data Interface 的跳转是可失败的——arrow-rs 的to_ffi/from_ffi都返回Result因为并非所有数组布局都能通过该接口表示——而不可失败的From遇到这些情况只能 panic。可失败的TryFrom/TryInto才是正确的特质最终交付的也正是它们。TryInto由 blanket impl 免费获得。机制早已在仓库中使用的 C Data Interface跨主版本转换必须零拷贝否则方案无法成立。机制就是 Arrow C Data Interface——它正是为此而生且仓库里已经在用apis/rust/operator/types/src/lib.rs调用了arrow::ffi::from_ffiInput/Output携带FFI_ArrowArray/FFI_ArrowSchema跨过#[repr(C)]边界。跳转路径为arrow58::ffi::to_ffi(data)→ 重新解释 →arrow::ffi::from_ffi。完整实现见 libraries/arrow-convert/src/ffi_bridge.rs。锋利的边缘类型不同的同名 FFI 结构体arrow58::ffi::FFI_ArrowArray与arrow::ffi::FFI_ArrowArray对 rustc 而言是截然不同的 Rust 类型尽管两者都是针对同一份冻结的 Arrow C ABI 的#[repr(C)]。桥接因此是一次 transmute / 按字段重新解释其健全性依赖于规范是稳定的这正是该规范存在的意义、且两个 crate 都忠实实现了它。这是不安全的胶水需要仔细的注释和测试。首先要测的是release回调较新的 Arrow 会调用较旧一方安装的 releaser。这正是接口设计的目的所在也恰是出错会变成 use-after-free 的地方。ffi_bridge 用编译期断言机械地约束健全性libraries/arrow-convert/src/ffi_bridge.rsconst _: () assert!( size_of::arrow58::ffi::FFI_ArrowArray() size_of::arrow::ffi::FFI_ArrowArray(), FFI_ArrowArray size differs between Arrow 58 and Arrow 59 ); const _: () assert!( align_of::arrow58::ffi::FFI_ArrowArray() align_of::arrow::ffi::FFI_ArrowArray(), FFI_ArrowArray alignment differs between Arrow 58 and Arrow 59 );FFI 桥的健全性论证可以总结为五点两个类型都是同一 C 结构的#[repr(C)]定义规范冻结且保证不变编译期断言捕获尺寸/对齐不匹配所有权恰好转移一次transmute是移动而非复制无双重释放点跨 crate 的release是设计内行为与 arrow-rs 释放 pyarrow 分配的内存的机制相同。一致性约束迫使源类型具体化一个泛型的implA: arrow58::array::Array TryFromA for DoraArray会被拒绝E0119它与 core 的implT, U: IntoT TryFromU for T重叠因为下游 crate 可能添加impl FromTheirType for DoraArrayRFC 2451 允许impl ForeignTraitLocalType for ForeignType而A可以实例化为TheirType。因此源类型是具体的dyn arrow58::array::Array和arrow58::array::ArrayRef后者单独提供因为Arcdyn Array在特质选择期间不会 unsize-coerce 为dyn Array。导出方向impl TryFromDoraArray for arrow58::array::ArrayRef是 RFC 2451 的形状可以原样接受。实现见 libraries/arrow-convert/src/ffi_bridge.rs三个TryFromimpl——dyn arrow58::array::Array → DoraArray、arrow58::array::ArrayRef → DoraArray、DoraArray → arrow58::array::ArrayRef。测试覆盖往返、嵌套与 release 回调ffi_bridge 的测试验证了设计中最脆弱的环节libraries/arrow-convert/src/ffi_bridge.rsround_trip_58_59_5858 → 59 → 58 的扁平原始类型往返数值与 schema 都必须存活round_trip_58_59_58_nested_and_nullable字符串三缓冲区、StructArray子列与空值位图覆盖 C 接口更复杂的部分release_callback_runs_when_import_is_dropped_unread/release_callback_runs_for_export_dropped_unread不读取导入/导出的数组直接 drop验证较新一方会精确调用较旧一方安装的 release 回调——这正是布局错误会变成 use-after-free 的地方双重释放会在此中止进程缺失 release 会让计数器不被置位round_trip_empty_array_preserves_type空数组仍需携带有效 schema 跨过跳转顺带覆盖ArrayRef源类型的导入路径。设计第四部分支持窗口策略添加arrow_vN特性是非破坏性的可在任何小版本中落地。移除一个特性是破坏性的必须等到主版本理想情况下在移除前先有若干版本携带#[deprecated]。按每年约 8 个 Arrow 主版本估算同时携带两到三个比较现实。该策略需要写入文档——这是本设计唯一持续的成本。该策略已写入 docs/api-rust.md 的 Support window 小节内部主版本从 59 迁移到 60 时arrow-v60被添加并成为免费/借用的那个arrow-v59继续工作但降级为经 C Data Interface 的转换型TryFrom对与今天的arrow-v58完全一样——没有任何东西静默改变含义。落地情况交付内容与设计的偏差设计文档的 What landed, and where it deviated 一节记录了最终实现与原始设计的三处偏差跨主版本转换是TryFrom/TryInto而非From/Into——C Data Interface 跳转可失败不可失败的From内部只能 panic。另有一致性约束迫使源类型具体化见上文第三部分。EncodedSample::data_type()被门控而非替换为类型 URN。dora_core::types::TypeRegistry只覆盖标准标量/结构体目录嵌套列表、字典、联合与带时区时间戳没有 URN因此返回 URN 的访问器对调用者最需要类型的输出恰好是有损的。实际实现是#[cfg(feature arrow-v59)]并配一个不加门控的type_name() - String用于日志见 apis/rust/node/src/node/mod.rs。DoraArray得到了同样的处理。一处 Arrow 类型的接缝被刻意保留。DoraArray位于dora-arrow-convert但必须构建和解包它的代码dora-node-api、C/C/Python 绑定、record/replay在其他 crate 中而 Rust 没有跨 crate 的pub(crate)。若经由arrow-v59特性路由则不可行dora-node-api必须无条件启用它Cargo 特性统一就会把不加门控的访问器交给每个下游用户——这正是门控要阻止的静默漂移。因此dora_arrow_convert::internal是pub、精神上#[doc(hidden)]、不从dora-node-api重导出并在 docs/api-rust.md 中明确声明其豁免于 semver 保证。dora-node-api自身的冻结表面是 Arrow 自由的——这才是真正重要的契约。internal模块的实现见 libraries/arrow-convert/src/internal.rs。迁移需要的两个额外产物设计未要求但迁移实际需要的两个补充arrow_utils::IpcPayload接收侧字节缓冲区的 DORA 自有包装使decode_arrow_ipc_zero_copy/InputDecoder::{set_schema, decode_batch}不再指名arrow::buffer::Buffer见 apis/rust/node/src/node/arrow_utils.rs。它同时消除了三处手写的Buffer::from_custom_allocationunsafe 代码块。针对 Arrow 59 数组类型的门控IntoArrow实现ArrayRef、PrimitiveArrayT、StructArray等使send_output(id, params, my_struct_array)对内部主版本上的调用者仍然可用。不能写implA: arrow::array::Array IntoArrow for A的 blanket 实现——它在一致性规则下与impl IntoArrow for u8重叠——所以具体类型被逐一列出。工作顺序先建DoraArray再重构 IPC 编码器表面设计文档给出了实施顺序引入DoraArray迁移ArrowData、IntoArrow、Event::Input及所有使用者。用门控的arrow_vN重导出替换pub use arrow;default []。重写泄漏的 IPC 编码器表面见下。以arrow-v58 FFI 桥TryFrom/TryInto作为旧主版本的实操范例。把支持窗口策略写入 docs/api-rust.md。其中 1–3 必须在 1.0 之前完成4–5 是可加项但 4 应与其余部分同时落地让转换路径有真实测试而非假设。第 3 步的细节泄漏的 IPC 编码器表面arrow_utils::ipc_encode是重导出模块内的pub mod因此encode_ipc_into、ipc_fast_path_len、encode_ipc_to_vec、encode_schema_message、batch_fast_path_len、encode_batch_into、schema_block_and_hash、encode_uint8_ipc_header、uint8_ipc_len、PreparedUint8Ipc与InputDecoder都是冻结的公共 API——其中大部分接受数组的函数指名arrow::array::ArrayData。设计文档修正了一个早期错误认知隐藏它们是不依赖其余部分的自由表面缩减——这双重错误。它们有真实的跨 crate 消费者binaries/record-node/src/main.rs ——ipc_fast_path_len、encode_ipc_into、encode_ipc_to_vecbinaries/daemon/src/lib.rs ——schema_block_and_hashapis/python/node/src/sample_handler.rs ——PreparedUint8Ipcapis/rust/node/benches/arrow_framing.rs 与 apis/rust/node/tests/copy_count.rs —— 经由公共路径到达它们copy_count.rs还使用encode_uint8_ipc_header/uint8_ipc_len因此pub(crate)会破坏三个 crate 外加一个 bench 和一个测试而#[doc(hidden)]只是对 rustdoc 隐藏——条目仍是公共且与 semver 相关的arrow::array::ArrayData会留在冻结 API 中这与目的相悖。正确做法为真实消费者保持公共但把接受数组的函数重新定义为接受DoraArray而非arrow::array::ArrayData——Arrow 在不隐藏、不破坏任何东西的情况下离开这些签名工作区内调用者传DoraArray新类型因此是免费的。schema_block_and_hash已经接受字节无需改动encode_schema_message(DataType)需要与第一部分data_type()相同的 DORA 自有类型处理。这解释了为什么该步骤排在DoraArray之后而非最先它并不独立于DoraArray而存在。实操速查节点中如何使用这些特性对最终用户契约已固化在 docs/api-rust.md 的 Arrow version policy 一节核心用法如下。依赖声明[dependencies] # 节点从不指名 Arrow 类型时什么都不需要 dora-node-api 1 # 指名 Arrow 59 —— DORA 当前内部主版本免费无转换且构建中不增加额外 Arrow 副本 dora-node-api { version 1, features [arrow-v59] }特性总览表特性重导出DoraArray访问器成本(无)—len、is_empty、null_count、type_name、TryFrom、into_vec—arrow-v59dora_node_api::arrow_v59as_array、into_inner、from_array、FromArrayRef免费借用arrow-v58dora_node_api::arrow_v58双向TryFrom/TryInto一次 C Data Interface 跳转无缓冲区复制跨主版本转换代码与非内部主版本的双向转换是可失败的C Data Interface 无法表示所有数组布局arrow-rs 以Result呈现因此是TryFrom/TryInto绝不是From/Intouse dora_node_api::{DoraArray, arrow_v58}; use arrow_v58::array::{Array, ArrayRef}; // Arrow 58 - dora。源类型是 dyn Array或 ArrayRef泛型 A: Array 实现不可行 let payload DoraArray::try_from(my_arrow58_array as dyn Array)?; // dora - Arrow 58 let back: ArrayRef (payload).try_into()?;常见路径完全不需特性Event::Input的数据以DoraArray到达用TryFromDoraArray转换bool、原始整数/浮点、String、str、chrono日期时间类型、[T]、VecT或into_vec::T()提取类型化值即可全程不必指名任何 Arrow 类型。支持窗口速查添加arrow-vN特性是可加的可在任何小版本落地。移除是破坏性的要等主版本且移除前至少一个版本带#[deprecated]。DORA 同时携带两到三个Arrow 主版本当前内部主版本加一两个较旧的。内部主版本迁移如 59 → 60时arrow-v60成为免费/借用型arrow-v59降级为经 C Data Interface 的转换型TryFrom对——与今天的arrow-v58完全一致没有任何东西静默改变含义。不在保证范围内dora_arrow_convert::internal是 DORA 自有 crate 使用的 Arrow 类型接缝C/C/Python 绑定、record/replay 节点。它不从dora-node-api重导出、指名 DORA 内部 Arrow 主版本、并且豁免于 semver 保证——内部主版本一变它就变。请使用版本门控的访问器替代。结语从设计到落地这条 Arrow 主版本解耦路径展示了一个完整的依赖治理范例识别冻结契约中的泄漏点 → 以自有新类型替换 → 用诚实命名的特性门控替代裸重导出 → 用稳定规范支撑零拷贝跨版本桥接 → 用明确的支持窗口策略管理生命周期成本。DoraArray与arrow_vN特性让 DORA 1.x 既能保住冻结的公共 API又能在小版本中跟进 Arrow 生态的演进TryFrom/TryInto与 C Data Interface 的 FFI 桥则保证了不同主版本共存时的类型安全与内存安全。想深入阅读实现细节可继续查看 libraries/arrow-convert/src/ffi_bridge.rs桥接实现与测试、libraries/arrow-convert/src/lib.rsDoraArray与IntoArrow、apis/rust/node/src/lib.rs特性化重导出以及用户侧契约文档 docs/api-rust.md。【免费下载链接】doraDORA (Dataflow-Oriented Robotic Architecture) is middleware designed to streamline and simplify the creation of AI-based robotic applications. It offers low latency, composable, and distributed dataflow capabilities. Applications are modeled as directed graphs, also referred to as pipelines.项目地址: https://gitcode.com/GitHub_Trending/do/dora创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门